Hrvatski

Naučite kako učinkovito koristiti funkcije za čišćenje React efekata kako biste spriječili curenje memorije i optimizirali performanse svoje aplikacije. Sveobuhvatan vodič.

React Effect Cleanup: Umijeće sprječavanja curenja memorije

Reactov useEffect hook moćan je alat za upravljanje nuspojavama (side effects) u vašim funkcionalnim komponentama. Međutim, ako se ne koristi ispravno, može dovesti do curenja memorije, što utječe na performanse i stabilnost vaše aplikacije. Ovaj sveobuhvatni vodič zaronit će u zamršenosti čišćenja React efekata, pružajući vam znanje i praktične primjere za sprječavanje curenja memorije i pisanje robusnijih React aplikacija.

Što su curenja memorije i zašto su loša?

Curenje memorije događa se kada vaša aplikacija alocira memoriju, ali je ne uspije osloboditi natrag u sustav kada više nije potrebna. S vremenom se ti neoslobođeni memorijski blokovi nakupljaju, trošeći sve više i više resursa sustava. U web aplikacijama, curenja memorije mogu se manifestirati kao:

U Reactu se curenja memorije često događaju unutar useEffect hookova prilikom rada s asinkronim operacijama, pretplatama ili osluškivačima događaja (event listeners). Ako se te operacije ne očiste pravilno kada se komponenta demontira (unmount) ili ponovno renderira, mogu nastaviti raditi u pozadini, trošeći resurse i potencijalno uzrokujući probleme.

Razumijevanje useEffect i nuspojava

Prije nego što zaronimo u čišćenje efekata, kratko se podsjetimo svrhe useEffect. Hook useEffect omogućuje vam izvođenje nuspojava u vašim funkcionalnim komponentama. Nuspojave su operacije koje stupaju u interakciju s vanjskim svijetom, kao što su:

Hook useEffect prihvaća dva argumenta:

  1. Funkciju koja sadrži nuspojavu.
  2. Opcionalni niz ovisnosti (dependencies).

Funkcija s nuspojavom izvršava se nakon što se komponenta renderira. Niz ovisnosti govori Reactu kada ponovno pokrenuti efekt. Ako je niz ovisnosti prazan ([]), efekt se pokreće samo jednom nakon početnog renderiranja. Ako je niz ovisnosti izostavljen, efekt se pokreće nakon svakog renderiranja.

Važnost čišćenja efekata

Ključ za sprječavanje curenja memorije u Reactu je čišćenje svih nuspojava kada više nisu potrebne. Tu na scenu stupa funkcija za čišćenje (cleanup function). Hook useEffect omogućuje vam da vratite funkciju iz funkcije s nuspojavom. Ta vraćena funkcija je funkcija za čišćenje, a izvršava se kada se komponenta demontira ili prije nego što se efekt ponovno pokrene (zbog promjena u ovisnostima).

Evo osnovnog primjera:


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

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

  useEffect(() => {
    console.log('Efekt se pokrenuo');

    // Ovo je funkcija za čišćenje
    return () => {
      console.log('Čišćenje se pokrenulo');
    };
  }, []); // Prazan niz ovisnosti: izvršava se samo jednom prilikom montiranja

  return (
    

Brojač: {count}

); } export default MyComponent;

U ovom primjeru, console.log('Efekt se pokrenuo') će se izvršiti jednom kada se komponenta montira. console.log('Čišćenje se pokrenulo') će se izvršiti kada se komponenta demontira.

Uobičajeni scenariji koji zahtijevaju čišćenje efekata

Istražimo neke uobičajene scenarije u kojima je čišćenje efekata ključno:

1. Tajmeri (setTimeout i setInterval)

Ako koristite tajmere u svom useEffect hooku, ključno je da ih obrišete kada se komponenta demontira. U suprotnom, tajmeri će se nastaviti aktivirati čak i nakon što komponente više nema, što dovodi do curenja memorije i potencijalno uzrokuje greške. Na primjer, zamislite automatski ažurirajući konverter valuta koji dohvaća tečajeve u intervalima:


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

function CurrencyConverter() {
  const [exchangeRate, setExchangeRate] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      // Simulacija dohvaćanja tečaja s API-ja
      const newRate = Math.random() * 1.2;  // Primjer: Nasumični tečaj između 0 i 1.2
      setExchangeRate(newRate);
    }, 2000); // Ažuriranje svake 2 sekunde

    return () => {
      clearInterval(intervalId);
      console.log('Interval obrisan!');
    };
  }, []);

  return (
    

Trenutni tečaj: {exchangeRate.toFixed(2)}

); } export default CurrencyConverter;

U ovom primjeru, setInterval se koristi za ažuriranje exchangeRate svake 2 sekunde. Funkcija za čišćenje koristi clearInterval kako bi zaustavila interval kada se komponenta demontira, sprječavajući tako da tajmer nastavi raditi i uzrokuje curenje memorije.

2. Osluškivači događaja (Event Listeners)

Kada dodajete osluškivače događaja u svom useEffect hooku, morate ih ukloniti kada se komponenta demontira. Ako to ne učinite, može doći do priključivanja više osluškivača na isti element, što dovodi do neočekivanog ponašanja i curenja memorije. Na primjer, zamislite komponentu koja osluškuje događaje promjene veličine prozora kako bi prilagodila svoj izgled različitim veličinama zaslona:


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

function ResponsiveComponent() {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      setWindowWidth(window.innerWidth);
    };

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
      console.log('Osluškivač događaja uklonjen!');
    };
  }, []);

  return (
    

Širina prozora: {windowWidth}

); } export default ResponsiveComponent;

Ovaj kod dodaje resize osluškivač događaja na prozor. Funkcija za čišćenje koristi removeEventListener za uklanjanje osluškivača kada se komponenta demontira, sprječavajući curenje memorije.

3. Pretplate (Websockets, RxJS Observables, itd.)

Ako se vaša komponenta pretplati na tok podataka pomoću websocketa, RxJS Observablea ili drugih mehanizama pretplate, ključno je odjaviti se kada se komponenta demontira. Ostavljanje aktivnih pretplata može dovesti do curenja memorije i nepotrebnog mrežnog prometa. Razmotrimo primjer gdje se komponenta pretplaćuje na websocket feed za cijene dionica u stvarnom vremenu:


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

function StockTicker() {
  const [stockPrice, setStockPrice] = useState(0);
  const [socket, setSocket] = useState(null);

  useEffect(() => {
    // Simulacija stvaranja WebSocket veze
    const newSocket = new WebSocket('wss://example.com/stock-feed');
    setSocket(newSocket);

    newSocket.onopen = () => {
      console.log('WebSocket povezan');
    };

    newSocket.onmessage = (event) => {
      // Simulacija primanja podataka o cijeni dionice
      const price = parseFloat(event.data);
      setStockPrice(price);
    };

    newSocket.onclose = () => {
      console.log('WebSocket prekinut');
    };

    newSocket.onerror = (error) => {
      console.error('WebSocket greška:', error);
    };

    return () => {
      newSocket.close();
      console.log('WebSocket zatvoren!');
    };
  }, []);

  return (
    

Cijena dionice: {stockPrice}

); } export default StockTicker;

U ovom scenariju, komponenta uspostavlja WebSocket vezu s feedom dionica. Funkcija za čišćenje koristi socket.close() za zatvaranje veze kada se komponenta demontira, sprječavajući da veza ostane aktivna i uzrokuje curenje memorije.

4. Dohvaćanje podataka s AbortControllerom

Prilikom dohvaćanja podataka u useEffect, posebno s API-ja kojima može trebati neko vrijeme da odgovore, trebali biste koristiti AbortController za otkazivanje zahtjeva za dohvaćanje ako se komponenta demontira prije nego što se zahtjev dovrši. To sprječava nepotreban mrežni promet i potencijalne greške uzrokovane ažuriranjem stanja komponente nakon što je demontirana. Evo primjera dohvaćanja korisničkih podataka:


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

function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/user', { signal });
        if (!response.ok) {
          throw new Error(`HTTP greška! status: ${response.status}`);
        }
        const data = await response.json();
        setUser(data);
      } catch (err) {
        if (err.name === 'AbortError') {
          console.log('Dohvaćanje prekinuto');
        } else {
          setError(err);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    return () => {
      controller.abort();
      console.log('Dohvaćanje prekinuto!');
    };
  }, []);

  if (loading) {
    return 

Učitavanje...

; } if (error) { return

Greška: {error.message}

; } return (

Korisnički profil

Ime: {user.name}

Email: {user.email}

); } export default UserProfile;

Ovaj kod koristi AbortController za prekidanje zahtjeva za dohvaćanje ako se komponenta demontira prije nego što se podaci dohvate. Funkcija za čišćenje poziva controller.abort() kako bi otkazala zahtjev.

Razumijevanje ovisnosti u useEffect

Niz ovisnosti u useEffect igra ključnu ulogu u određivanju kada se efekt ponovno pokreće. Također utječe na funkciju za čišćenje. Važno je razumjeti kako ovisnosti funkcioniraju kako bi se izbjeglo neočekivano ponašanje i osiguralo pravilno čišćenje.

Prazan niz ovisnosti ([])

Kada navedete prazan niz ovisnosti ([]), efekt se pokreće samo jednom nakon početnog renderiranja. Funkcija za čišćenje pokrenut će se samo kada se komponenta demontira. To je korisno za nuspojave koje je potrebno postaviti samo jednom, kao što je inicijalizacija websocket veze ili dodavanje globalnog osluškivača događaja.

Ovisnosti s vrijednostima

Kada navedete niz ovisnosti s vrijednostima, efekt se ponovno pokreće kad god se bilo koja od vrijednosti u nizu promijeni. Funkcija za čišćenje izvršava se *prije* ponovnog pokretanja efekta, omogućujući vam da očistite prethodni efekt prije postavljanja novog. To je važno za nuspojave koje ovise o određenim vrijednostima, kao što je dohvaćanje podataka na temelju korisničkog ID-a ili ažuriranje DOM-a na temelju stanja komponente.

Razmotrite ovaj primjer:


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

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

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      try {
        const response = await fetch(`https://api.example.com/users/${userId}`);
        const result = await response.json();
        if (!didCancel) {
          setData(result);
        }
      } catch (error) {
        console.error('Greška pri dohvaćanju podataka:', error);
      }
    };

    fetchData();

    return () => {
      didCancel = true;
      console.log('Dohvaćanje otkazano!');
    };
  }, [userId]);

  return (
    
{data ?

Korisnički podaci: {data.name}

:

Učitavanje...

}
); } export default DataFetcher;

U ovom primjeru, efekt ovisi o userId propu. Efekt se ponovno pokreće kad god se userId promijeni. Funkcija za čišćenje postavlja zastavicu didCancel na true, što sprječava ažuriranje stanja ako se zahtjev za dohvaćanje dovrši nakon što se komponenta demontirala ili se userId promijenio. Ovo sprječava upozorenje "Can't perform a React state update on an unmounted component".

Izostavljanje niza ovisnosti (koristiti s oprezom)

Ako izostavite niz ovisnosti, efekt se pokreće nakon svakog renderiranja. To se općenito ne preporučuje jer može dovesti do problema s performansama i beskonačnih petlji. Međutim, postoje rijetki slučajevi u kojima bi to moglo biti potrebno, kao što je kada trebate pristupiti najnovijim vrijednostima propova ili stanja unutar efekta bez da ih eksplicitno navedete kao ovisnosti.

Važno: Ako izostavite niz ovisnosti, *morate* biti izuzetno oprezni s čišćenjem bilo kakvih nuspojava. Funkcija za čišćenje će se izvršavati prije *svakog* renderiranja, što može biti neučinkovito i potencijalno uzrokovati probleme ako se ne rukuje ispravno.

Najbolje prakse za čišćenje efekata

Evo nekih najboljih praksi koje treba slijediti pri korištenju čišćenja efekata:

Alati za otkrivanje curenja memorije

Nekoliko alata može vam pomoći u otkrivanju curenja memorije u vašim React aplikacijama:

Zaključak

Usvajanje čišćenja React efekata ključno je za izgradnju robusnih, učinkovitih i memorijski efikasnih React aplikacija. Razumijevanjem principa čišćenja efekata i slijedeći najbolje prakse navedene u ovom vodiču, možete spriječiti curenje memorije i osigurati glatko korisničko iskustvo. Ne zaboravite uvijek čistiti nuspojave, paziti na ovisnosti i koristiti dostupne alate za otkrivanje i rješavanje potencijalnih curenja memorije u vašem kodu.

Marljivom primjenom ovih tehnika možete podići svoje vještine razvoja u Reactu i stvarati aplikacije koje nisu samo funkcionalne, već i performantne i pouzdane, pridonoseći boljem ukupnom korisničkom iskustvu za korisnike diljem svijeta. Ovaj proaktivni pristup upravljanju memorijom razlikuje iskusne programere i osigurava dugoročnu održivost i skalabilnost vaših React projekata.

React Effect Cleanup: Umijeće sprječavanja curenja memorije | MLOG