Co to jest useEffect() i jak działa?

React

readTime

15 min

Co to jest useEffect() i jak działa?

Spis treści

Wprowadzenie Hooków w Reakcie zmieniło sposób, w jaki tworzymy komponenty, a useEffect jest jednym z najpopularniejszych i wraz z useState najczęściej stosowanym.

Jest to nic innego niż nowa wersja starych funkcji z komponentów klasowych, ale prostsza w użyciu.

useEffect pomaga nam lepiej zarządzać tym, co dzieje się, gdy komponent jest montowany, aktualizowany lub usuwany, w sposób znacznie bardziej zrozumiały.

W tym wpisie wyjaśnię wszystkie szczegóły dotyczące hooka useEffect, pokazując jego istotną rolę w komponentach funkcyjnych Reacta i jak upraszcza zarządzanie cyklem życia komponentów.

Czym jest useEffect? 💡

Hook useEffect jest narzędziem do obsługi efektów ubocznych w komponentach funkcyjnych.

Efekty uboczne to wszelkie operacje, które wchodzą w interakcje ze światem zewnętrznym, takie jak pobieranie danych, subskrypcje lub manipulacja DOM (Obiektowym Modelem Dokumentu).

useEffect pozwala na wykonywanie takich akcji w sposób zarówno efektywny, jak i zorganizowany.

Jak używać hooka useEffect 🛠️

Podstawowa składnia useEffect jest prosta:

js
useEffect(() => {
  // Twój kod efektu ubocznego tutaj.
  // Ten kod uruchamia się po każdym renderowaniu komponentu.

  return () => {
    // Opcjonalny kod czyszczący tutaj.
    // Ten kod uruchamia się, gdy komponent jest usuwany.
  };
}, [dependencies]); // Efekt uruchomi się ponownie tylko wtedy, gdy te zależności się zmienią.
  • Funkcja Efektu: Funkcja przekazana do useEffect uruchamia się po wyrenderowaniu komponentu. Idealnie nadaje się do aktualizacji DOM, pobierania danych, ustawiania subskrypcji itp.
  • Tablica Zależności: Drugi argument to tablica zależności. useEffect uruchomi się ponownie tylko wtedy, gdy te zależności zmienią się między ponownym renderowaniem.

Jeśli podasz pustą tablicę [], efekt uruchomi się raz gdy komponent zostanie wyrenderowany, lucz inaczej mówiąc zamontowany, imitując zachowanie componentDidMount w komponentach klasowych.

  • Funkcja Czyszcząca: Opcjonalnie, twoja funkcja efektu może zwrócić funkcję czyszczącą. React wywoła tę funkcję przed ponownym uruchomieniem efektu i gdy komponent jest usuwany. To idealne miejsce na operacje czyszczące, podobne do componentWillUnmount w komponentach klasowych.

Przejście z Komponentów Klasowych do Funkcyjnych 🔁

We wcześniejszych wersjach Reacta, komponenty klasowe były standardem do zarządzania złożonym stanem i zdarzeniami cyklu życia.

Te komponenty używały specyficznych metod cyklu życia do wykonywania kodu na różnych etapach życia komponentu. Spójrzmy na wspólny przykład:

Metody Cyklu Życia w Komponentach Klasowych

Komponenty klasowe w React używają metod cyklu życia na różnych etapach życia komponentu. Oto typowy wzorzec korzystający z tych metod:

js
class ExampleClassComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = { count: 0 };
    }

    componentDidMount() {
        document.title = `Kliknięto ${this.state.count} razy`;
    }

    componentDidUpdate() {
        document.title = `Kliknięto ${this.state.count} razy`;
    }

    componentWillUnmount() {
        alert('Komponent jest usuwany');
    }

    render() {
        return (
            <div>
                <p>Kliknięto {this.state.count} razy</p>
                <button onClick={() => this.setState({ count: this.state.count + 1 })}>
                    Kliknij mnie
                </button>
            </div>
        );
    }
}

W tym przykładzie, componentDidMount jest używany do akcji po wstawieniu komponentu do DOM, componentDidUpdate obsługuje zmiany stanu lub właściwości, a componentWillUnmount jest do czyszczenia przed usunięciem komponentu z DOM.

Komponenty Funkcyjne: Wprowadzenie Hooków

Dzięki wprowadzeniu Hooków, te metody cyklu życia mogą być teraz obsługiwane w komponentach funkcyjnych, które są zazwyczaj bardziej zwięzłe i łatwiejsze do odczytu oraz testowania.

To samo ale z pomocą useEffect w Komponentach Funkcyjnych

js
import React, { useState, useEffect } from 'react';

function ExampleFunctionalComponent() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        // Uruchamia się po każdym renderze (montowanie i aktualizowanie)
        document.title = `Kliknięto ${count} razy`;

        // Kod czyszczący (do usuwania)
        return () => {
            alert('Komponent jest usuwany');
        };
    }, [count]); // Efekt uruchomi się ponownie tylko, jeśli count się zmieni

    return (
        <div>
            <p>Kliknięto {count} razy</p>
            <button onClick={() => setCount(count + 1)}>
                Kliknij mnie
            </button>
        </div>
    );
}

W komponencie funkcyjnym, useEffect jest używany do obsługi efektów ubocznych.

Pierwszy argument to funkcja, która uruchamia się po każdym renderze. Zastępuje zarówno componentDidMount, jak i componentDidUpdate, ponieważ można go skonfigurować do uruchamiania tylko wtedy, gdy określone wartości (jak count) ulegną zmianie.

Funkcja zwracana wewnątrz useEffect to mechanizm czyszczący, podobny do componentWillUnmount w komponentach klasowych.

Przykłady Praktyczne - Zrozumienie useEffect w Akcji 🚀

Teraz przeanalizujemy trzy różne przykłady, aby zademonstrować Ci, jak hook useEffect działa jako odpowiednik tradycyjnych metod cyklu życia w komponentach klasowych.

Te przykłady obejmują pobieranie danych (componentDidMount), reagowanie na zmiany stanu (componentDidUpdate) i operacje czyszczące (componentWillUnmount).

Przykład 1: Pobieranie Danych przy Montowaniu (componentDidMount)

Hook useEffect pozwala na wykonywanie akcji, gdy komponent jest renderowany po raz pierwszy, podobnie jak componentDidMount w komponentach klasowych. Zobaczmy to w akcji podczas pobierania danych:

Pobieranie Danych z useEffect

js
import React, { useState, useEffect } from 'react';

function DataFetcher() {
    const [data, setData] = useState(null);

    useEffect(() => {
        fetch('https://jsonplaceholder.typicode.com/posts/1')
            .then(response => response.json())
            .then(post => setData(post));
    }, []);

    if (!data) {
        return <div>Ładowanie...</div>;
    }

    return <div>Tytuł: {data.title}</div>;
}

Ten przykład pokazuje, jak hooka useEffect można użyć do pobierania danych, gdy komponent się montuje.

Użyliśmy pustej tablicy zależności, aby upewnić się, że efekt uruchomi się tylko raz, podobnie jak componentDidMount.

Przykład 2: Reagowanie na Zmiany Stanu (componentDidUpdate)

Następnie użyjemy useEffect do wykonania akcji w odpowiedzi na zmianę stanu, naśladując funkcjonalność componentDidUpdate.

js
import React, { useState, useEffect } from 'react';

function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        document.title = `Liczba: ${count}`;
    }, [count]);

    return (
        <div>
            <p>Liczba: {count}</p>
            <button onClick={() => setCount(count + 1)}>Zwiększ</button>
        </div>
    );
}

Ten komponent Counter używa useEffect do aktualizacji tytułu dokumentu za każdym razem, gdy zmienia się stan count.

Zależność od [count] zapewnia, że efekt działa podobnie do componentDidUpdate, reagując na zmiany w określonych wartościach.

Przykład 3: Operacje Czyszczące (componentWillUnmount)

Na koniec zobaczymy, jak useEffect można użyć do czyszczenia lub wykonywania końcowych operacji przed odmontowaniem komponentu, jak componentWillUnmount.

js
import React, { useState, useEffect } from 'react';

function TimerComponent() {
    const [seconds, setSeconds] = useState(0);

    useEffect(() => {
        const intervalId = setInterval(() => {
            setSeconds(prevSeconds => prevSeconds + 1);
        }, 1000);

        return () => clearInterval(intervalId);
    }, []);

    return <div>Stoper: {seconds} sekund</div>;
}

Komponent TimerComponent ustawia licznik, który zwiększa się co sekundę.

Funkcja zwracana w useEffect to funkcja czyszcząca, która czyści interwał, zapewniając, że nie pozostaną żadne efekty uboczne, gdy komponent zostanie odmontowany, podobnie do componentWillUnmount.

Te przykłady pokazują, jak łatwo jest używać hooka useEffect do różnych elementów życia komponentu w Reakcie.

Niezależnie od tego, czy chodzi o pobieranie danych podczas montowania komponentu, aktualizowanie czegoś przy zmianie stanu, czy czyszczenia przy odmontowywaniu komponentu, useEffect sprawia, że wszystko jest łatwe i proste.

Zrozumienie useEffect z Różnymi Typami Zależności 🧭

Zachowanie hooka useEffect może znacznie się różnić w zależności od typów zależności. Przyjrzyjmy się bliżej, jak działa z obiektami, null, undefined i prymitywnymi typami, takimi jak ciągi znaków (stringi) i liczby.

1. Brak Tablicy Zależności

Jeśli pominiemy tablicę zależności, useEffect uruchamia się po każdym renderze.

js
useEffect(() => {
    console.log('To uruchamia się po każdym renderze');
});

Bez tablicy zależności efekt uruchamia się po każdym renderze komponentu. Jest to przydatne, gdy masz efekt, który musi się aktualizować za każdym razem, gdy komponent się aktualizuje, niezależnie od tego, co spowodowało aktualizację.

2. Pusta Tablica []

Pusta tablica oznacza, że efekt uruchamia się raz po początkowym renderze, podobnie do componentDidMount.

js
useEffect(() => {
    console.log('To uruchamia się raz, po początkowym renderze');
}, []);

Z pustą tablicą efekt zachowuje się jak metoda cyklu życia componentDidMount w komponentach klasowych.

Uruchamia się raz po początkowym renderze, a potem nigdy więcej. Jest to idealne dla operacji, które muszą być wykonane tylko jednokrotnie.

3. Tablica ze Specyficznymi Wartościami

Kiedy dołączysz specyficzne wartości do tablicy, efekt uruchamia się, gdy te wartości ulegną zmianie.

js
const [count, setCount] = useState(0);

useEffect(() => {
    console.log('To uruchamia się, gdy "count" się zmienia');
}, [count]);

Tutaj efekt uruchamia się za każdym razem, gdy zmienia się stan count. Jest to sposób na reakcję efektu na zmiany w określonych danych, podobnie do componentDidUpdate dla specyficznych właściwości lub wartości stanu.

4. Użycie Obiektów jako Zależności

Tablica zależności Reacta nie wykonuje głębokiego porównania. Oznacza to, że gdy używasz obiektów jako zależności, React porównuje tylko ich referencje, a nie zawartość.

js
const [user, setUser] = useState({ name: "John", age: 30 });

useEffect(() => {
    console.log('Efekt uruchamia się, jeśli zmienia się referencja obiektu "user"');
}, [user]);

W tym fragmencie useEffect uruchomi się ponownie tylko wtedy, gdy referencja obiektu user się zmieni, a nie jego zawartość.

Na przykład, jeśli zaktualizujesz wiek użytkownika, zachowując tę samą referencję obiektu, efekt nie uruchomi się ponownie. To zachowanie często prowadzi do błędów i generalnie nie jest zalecane, chyba że celowo chcesz śledzić zmiany referencji obiektu.

5. null i undefined

Użycie null lub undefined jako zależności ma specyficzny efekt: tablica zależności zachowuje się tak, jakby jej nie było.

js
useEffect(() => {
    console.log('To uruchamia się po każdym renderze, jak bez tablicy zależności');
}, null); // lub undefined

W tym przypadku efekt uruchamia się po każdym renderze komponentu, co jest domyślnym zachowaniem, gdy tablica zależności nie jest dostarczona.

Jest to podobne do posiadania efektu bez wymienionych zależności.

Zrozumienie, jak różne typy zależności wpływają na useEffect, jest kluczowe dla jego efektywnego wykorzystania.

Podczas gdy prymitywne typy, takie jak stringi i liczby, są proste i niezawodne, obiekty wymagają ostrożnego obchodzenia się ze względu na płytkie porównanie tzw. shallow comparison.

Tymczasem null i undefined usuwają sprawdzanie zależności, powodując uruchomienie efektu po każdym renderze.

Znając te niuanse, można pisać bardziej efektywne i bezbłędne komponenty React.

Najczęściej popełniane błędy i dobre praktyki przy używaniu useEffect ⚠️💡

Korzystanie z hooka useEffect może czasem być nie do końca zrozumiałe,dlatego teraz pokażę Ci kilka typowych problemów, z którymi często borykają się początkujący:

Najczęściej popełniane błędy

  1. Nieskończone pętle renderowania:

    • Opis Problemu: Nieskończone pętle mogą wystąpić, gdy useEffect zmienia stan lub właściwości, które są również zależnościami w tej samej tablicy zależności. Powoduje to, że efekt uruchamia się ponownie bez końca.
    • Rozwiązanie: Upewnij się, że nie aktualizujesz stanu wewnątrz useEffect, jeśli ten stan jest również w tablicy zależności. Rozważ użycie useRef do przechowywania wartości, które nie wymagają ponownego renderowania komponentu.
    js
    useEffect(() => {
        const id = setInterval(() => {
            setCount(prevCount => prevCount + 1);
        }, 1000);
        
        return () => clearInterval(id);
    }, []); // Użycie pustej tablicy, aby efekt uruchomił się tylko raz
    
  2. Wyciek pamięci:

    • Opis Problemu: Jeśli useEffect zawiera asynchroniczne operacje (np. fetch), które nie są odpowiednio czyszczone, może dojść do wycieku pamięci.
    • Rozwiązanie: Zawsze zwracaj funkcję czyszczącą z useEffect, aby upewnić się, że nie pozostaną niepotrzebne subskrypcje lub operacje.
    js
    useEffect(() => {
        const controller = new AbortController();
        fetch('https://api.example.com/data', { signal: controller.signal })
            .then(response => response.json())
            .then(data => setData(data))
            .catch(error => {
                if (error.name === 'AbortError') return;
                console.error('Fetch error:', error);
            });
        
        return () => controller.abort();
    }, []); // Upewnij się, że efekt uruchamia się tylko raz
    
  3. Niepełne tablice zależności:

    • Opis Problemu: Pominięcie zmiennych w tablicy zależności może prowadzić do nieoczekiwanych zachowań, ponieważ useEffect nie uruchomi się ponownie, gdy te zmienne się zmienią.
    • Rozwiązanie: Upewnij się, że wszystkie zmienne używane wewnątrz useEffect są wymienione w tablicy zależności.
    js
    const [count, setCount] = useState(0);
    const [multiplier, setMultiplier] = useState(1);
    
    useEffect(() => {
        console.log(`Count multiplied by multiplier: ${count * multiplier}`);
    }, [count, multiplier]); // Upewnij się, że wszystkie zależne zmienne są wymienione
    

Dobre praktyki

  1. Używaj odpowiednich tablic zależności:

    • W praktyce: Dokładnie określ, jakie zależności są potrzebne do ponownego uruchomienia useEffect. Pusta tablica oznacza, że efekt uruchomi się tylko raz, po początkowym renderze.
    js
    useEffect(() => {
        console.log('Uruchamia się tylko raz po początkowym renderze');
    }, []); // Efekt uruchamia się tylko raz
    
  2. Podziel efekty na różne useEffect:

    • W praktyce: Unikaj umieszczania wielu różnych logik w jednym useEffect. Zamiast tego rozdziel efekty na kilka useEffect dla lepszej czytelności i izolacji logiki.
    js
    useEffect(() => {
        document.title = `Liczba kliknięć: ${count}`;
    }, [count]); // Efekt do aktualizacji tytułu
    
    useEffect(() => {
        const intervalId = setInterval(() => {
            setSeconds(prevSeconds => prevSeconds + 1);
        }, 1000);
        
        return () => clearInterval(intervalId);
    }, []); // Efekt do ustawiania licznika
    
  3. Korzystaj z funkcji czyszczących:

    • W praktyce: Zawsze zwracaj funkcję czyszczącą z useEffect, aby uniknąć wycieków pamięci i niepożądanych efektów ubocznych.
    js
    useEffect(() => {
        const subscription = someObservable.subscribe(data => {
            setData(data);
        });
    
        return () => subscription.unsubscribe(); // Funkcja czyszcząca subskrypcję
    }, []); // Upewnij się, że efekt uruchamia się tylko raz
    
  4. Unikaj niepotrzebnych efektów:

    • W praktyce: Unikaj umieszczania efektów, które nie muszą być aktualizowane przy każdej zmianie stanu. Przemyśl, które efekty rzeczywiście wymagają aktualizacji.
    js
    const [count, setCount] = useState(0);
    
    useEffect(() => {
        document.title = `Liczba kliknięć: ${count}`;
    }, [count]); // Efekt uruchamia się tylko, gdy zmienia się `count`
    

Stosowanie się do tych dobrych praktyk i unikanie najczęściej popełnianych błędów pomoże w tworzeniu bardziej wydajnych i łatwych do utrzymania aplikacji Reaktowych.

FAQ: Najczęściej Zadawane Pytania na Temat useEffect podczas rozmowy kwalifikacyjnej 🚀

1. Czym jest hook useEffect w Reakcie?

  • Odpowiedź: useEffect to hook, który pozwala zarządzać efektami ubocznymi w komponentach funkcyjnych.

Efekty uboczne to operacje, które wchodzą w interakcje ze światem zewnętrznym, takie jak pobieranie danych, subskrypcje lub manipulacja DOM. useEffect uruchamia się po wyrenderowaniu komponentu i może również zwracać funkcję czyszczącą, która uruchamia się przed ponownym renderowaniem lub usunięciem komponentu.

2. Jakie są różnice między componentDidMount, componentDidUpdate i componentWillUnmount a useEffect?

  • Odpowiedź: useEffect w pewnym sensie łączy funkcjonalności componentDidMount, componentDidUpdate i componentWillUnmount:
    • componentDidMount: useEffect z pustą tablicą zależności [] uruchamia się raz po początkowym renderze, podobnie jak componentDidMount.
    • componentDidUpdate: useEffect z określonymi zależnościami uruchamia się po każdym renderze, jeśli te zależności się zmieniają, co odpowiada componentDidUpdate.
    • componentWillUnmount: useEffect może zwracać funkcję czyszczącą, która działa przed usunięciem komponentu lub przed ponownym uruchomieniem efektu, co odpowiada componentWillUnmount.

3. Jak działa tablica zależności w useEffect?

  • Odpowiedź: Tablica zależności w useEffect określa, kiedy efekt powinien się uruchamiać ponownie. Jeśli tablica zależności jest pusta [], efekt uruchomi się tylko raz po początkowym renderze. Jeśli zawiera określone zmienne, efekt uruchomi się ponownie za każdym razem, gdy którakolwiek z tych zmiennych się zmieni. Bez tablicy zależności, efekt uruchamia się po każdym renderze komponentu, bez względu na to co go aktualizuje.

4. Dlaczego useEffect może powodować nieskończone pętle renderowania i jak temu zapobiec?

  • Odpowiedź: useEffect może powodować nieskończone pętle, jeśli zmienia stan lub właściwości, które są zależnościami w tej samej tablicy zależności. Powoduje to, że efekt uruchamia się ponownie bez końca. Aby temu zapobiec, należy upewnić się, że nie aktualizujemy stanu wewnątrz useEffect, jeśli ten stan jest również w tablicy zależności, oraz używać funkcji czyszczących do zarządzania efektami ubocznymi.

5. Jak można wykorzystać useEffect do pobierania danych tylko raz, po początkowym wyrenderowaniu komponentu?

  • Odpowiedź: Aby użyć useEffect do pobierania danych tylko raz po początkowym renderze, należy użyć pustej tablicy zależności []. Spowoduje to, że efekt uruchomi się tylko raz, zaraz po pierwszym renderze komponentu.
js
useEffect(() => {
    fetch('https://api.example.com/data')
        .then(response => response.json())
        .then(data => setData(data));
}, []); // Pusta tablica zależności

6. Jak zrealizować operacje czyszczące przy użyciu useEffect?

  • Odpowiedź: Operacje czyszczące można zrealizować zwracając funkcję z useEffect. Ta funkcja będzie wywoływana przed ponownym uruchomieniem efektu lub przed usunięciem komponentu.
javascriptowa
useEffect(() => {
    const subscription = someObservable.subscribe(data => {
        setData(data);
    });

    return () => subscription.unsubscribe(); // Funkcja czyszcząca
}, []); // Pusta tablica zależności

7. Co się stanie, jeśli w tablicy zależności useEffect użyjemy obiektów?

  • Odpowiedź: React wykonuje płytkie porównania w tablicy zależności useEffect. Oznacza to, że przy użyciu obiektów jako zależności, React porówna tylko referencje obiektów, a nie ich zawartość. Może to prowadzić do niespodziewanych zachowań, jeśli referencja obiektu się nie zmieni, ale jego zawartość tak.

8. Jakie są najlepsze praktyki dotyczące korzystania z useEffect?

  • Odpowiedź: Najlepsze praktyki obejmują:
    • Precyzyjne określanie zależności w tablicy zależności.
    • Oddzielanie różnych logik do osobnych useEffect dla lepszej czytelności.
    • Używanie funkcji czyszczących do unikania wycieków pamięci.
    • Unikanie aktualizacji stanu wewnątrz useEffect, jeśli ten stan jest zależnością.

9. Czy useEffect może być użyty do nasłuchiwania na zmiany w kontekście zewnętrznym, np. zmiany rozmiaru okna?

  • Odpowiedź: Tak, useEffect może być użyty do nasłuchiwania na zmiany w kontekście zewnętrznym, takie jak zmiany rozmiaru okna. Można to zrobić, dodając nasłuchiwacz zdarzeń w useEffect i usuwając go w funkcji czyszczącej.
js
useEffect(() => {
    const handleResize = () => {
        console.log('Window resized');
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize); // Funkcja czyszcząca
}, []); // Pusta tablica zależności, efekt uruchomi się raz

10. Jak unikać powszechnych pułapek związanych z useEffect?

  • Odpowiedź:
    • Zawsze upewniaj się, że wszystkie zależności są poprawnie wymienione.
    • Używaj funkcji czyszczących, aby zapobiegać wyciekom pamięci.
    • Unikaj modyfikacji stanu, który jest zależnością.
    • Testuj efekty, aby upewnić się, że uruchamiają się zgodnie z oczekiwaniami.

Stosowanie się do tych zasad pomoże uniknąć wielu problemów związanych z useEffect i pozwoli na efektywne zarządzanie efektami ubocznymi w komponentach Reactowych.

Podsumowanie ✅

W tym wpisie wyjaśniłem Ci jak hook useEffect pomaga w bibliotece React zarządzać zachowaniem komponentu w sposób prosty i efektywny.

Jest świetny do zadań takich jak pobieranie danych, aktualizowanie komponentu i czyszczenie.

Pamiętaj tylko, aby używać go ostrożnie, aby unikać typowych problemów.Gdy wiesz jak działa useEffect i jak go prawidłowo używać, wówczas możesz tworzyć naprawdę ciekawe projekty.

Dzięki za przeczytanie i do zobaczenia w kolejnym wpisie 🎉

authorImg

Witek Pruchnicki

Z pasją dzielę się wiedzą o programowaniu i nie tylko na różne sposoby