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:

  1. Funkcję efektu oraz tablicę zależności. Funkcja efektu jest miejscem, gdzie umieszczamy nasz kod efektu ubocznego.
  2. 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?

  1. 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.
  2. 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.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *