W kontekście aplikacji React, efektem ubocznym (side effect) jest każda operacja, która wpływa na coś poza zwracanym przez komponent wynikiem — np. operacje na DOM, zapytania do API, ustawianie timera itp.
Ale co może się stać jak nie będziemy zarządzać efektami ubocznymi?
Załóżmy, że mamy komponent React, który aktualizuje stan na podstawie wyniku zapytania do API.
Przykład takiego błędu może wyglądać następująco:
import React, { useState } from 'react';
function UserStatus({ userId }) {
const [status, setStatus] = useState('loading');
fetch(`https://api.example.com/user-status/${userId}`)
.then(response => response.json())
.then(data => {
setStatus(data.status); // To spowoduje ponowne renderowanie
});
return <div>User status: {status}</div>;
}
Gdzie tu jest problem?
Nieskończona pętla renderowania: W tym przykładzie, kiedy komponent UserStatus
renderuje się po raz pierwszy, natychmiast wykonuje zapytanie do API. Kiedy wyniki zapytania są gotowe, aktualizuje stan za pomocą setStatus
, co z kolei powoduje ponowne renderowanie komponentu. Ponieważ ponowne renderowanie uruchamia znowu całe ciało funkcji komponentu, zapytanie do API jest wykonane ponownie, a po jego ukończeniu stan jest aktualizowany, co prowadzi do kolejnego renderowania — i tak dalej.
Obciążenie serwera i aplikacji: Każde ponowne renderowanie powoduje kolejne zapytanie do API, co niepotrzebnie obciąża zarówno serwer, jak i przeglądarkę użytkownika, potencjalnie prowadząc do spowolnienia lub zawieszenia aplikacji.
Jak tego Unikać?
Problem ten można uniknąć, stosując hook useEffect
do wykonania efektu ubocznego związanego z zapytaniem do API. Użycie useEffect
pozwala nam ograniczyć wykonanie efektu tylko do momentów, które sami określimy — na przykład przy montowaniu komponentu lub przy zmianie wartości userId
. Oto poprawiona wersja przykładu z wykorzystaniem useEffect
:
import React, { useState, useEffect } from 'react';
function UserStatus({ userId }) {
const [status, setStatus] = useState('loading');
useEffect(() => {
fetch(`https://api.example.com/user-status/${userId}`)
.then(response => response.json())
.then(data => {
setStatus(data.status);
});
}, [userId]); // Efekt wykonuje się tylko wtedy, gdy `userId` się zmienia
return <div>User status: {status}</div>;
}
useEffect
Hook useEffect
przyjmuje dwa argumenty:
- Funkcję efektu oraz tablicę zależności. Funkcja efektu jest miejscem, gdzie umieszczamy nasz kod efektu ubocznego.
- Tablica zależności to lista wartości, przy zmianie których React ponownie uruchomi efekt.
Przekazanie pustej tablicy zależności ([]
) do hooka useEffect
w React ma szczególny i bardzo ważny efekt: powoduje, że dany efekt uboczny zostanie uruchomiony dokładnie jeden raz — w momencie montowania komponentu. Innymi słowy, takie użycie useEffect
odpowiada za wykonanie operacji, które mają się wydarzyć tylko raz i nie wymagają ponownego uruchamiania w cyklu życia komponentu.
Czyszczenie efektu
Niektóre efekty uboczne, jak subskrypcje czy timery, wymagają czyszczenia, aby uniknąć wycieków pamięci. useEffect
pozwala na to poprzez zwrócenie funkcji, która wykonuje czyszczenie:
import React, { useState, useEffect } from 'react';
function TimerComponent() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);
}, []);
return <div>Timer: {seconds} seconds</div>;
}
Gdy komponent TimerComponent
zostanie odmontowany z drzewa DOM, interwał ustawiony w useEffect
będzie nadal działał w tle, próbując aktualizować stan komponentu, który już nie istnieje.
Kontynuowanie działania niepotrzebnych interwałów lub subskrypcji może prowadzić do wycieków pamięci, co w skrajnych przypadkach może zwiększyć zużycie zasobów przeglądarki i wpłynąć na wydajność użytkownika.
useEffect(() => {
const timerId = setTimeout(() => {
console.log('To się wykona po 1 sekundzie');
}, 1000);
// Funkcja czyszcząca
return () => clearTimeout(timerId);
}, []);
Kiedy funkcja czyszcząca jest wywoływana?
- Jeśli
useEffect
jest uruchamiany ponownie z powodu zmiany w wartościach z jego tablicy zależności, React najpierw wywoła funkcję czyszczącą z poprzedniego renderowania, zanim uruchomi efekt ponownie. - Kiedy komponent, w którym zdefiniowano
useEffect
, jest usuwany z drzewa DOM, React wywoła funkcję czyszczącą, dając nam szansę na posprzątanie zasobów i uniknięcie potencjalnych wycieków pamięci.