Ako svakodnevno radite s Reactom, uskoro ćete otkriti da savladavanje useState i useEffect funkcija čini svu razliku između komponente koja "radi povremeno" i solidnog, održivog i lakog za otklanjanje grešaka interfejsa. Oba hooka na prvi pogled izgledaju jednostavno, ali čim počnete slati HTTP zahtjeve, pretplate ili koristiti tajmere, javljaju se sumnje, efekti se izvode u petljama, a stanja zastarjevaju.
U sljedećim redovima, vidjet ćemo kako pravilno koristiti `useState` i `useEffect` u Reactu, od osnova do naprednih obrazaca , integrirajući najbolje prakse za performanse, upravljanje ovisnostima, čišćenje efekata, korištenje s vanjskim API-jima i neke trikove za izbjegavanje uobičajenih grešaka. Cilj je da do kraja budete u mogućnosti čitati bilo koju komponentu s hookovima i razumjeti što se događa, a prije svega, da znate kako dizajnirati vlastitu komponentu bez straha od kvara.
Šta su tačno useState i useEffect i kada ih treba koristiti?
React je uveo Hookove počevši od verzije 16.8 za upravljanje stanjem i logikom životnog ciklusa unutar funkcionalnih komponenti bez korištenja klasa. Cilj je omogućiti deklarativno sastavljanje logike za višekratnu upotrebu, koristeći sam JavaScript jezik (closures, čiste funkcije itd.).
Kuka useState Koristi se za dodavanje lokalno stanje funkcionalne komponenteUmjesto da imam ovo.stanje y this.setStateProglašavate par [valor, setValor] React će povezati redoslijed poziva vaših hook funkcija u toj komponenti s tim stanjem. Svaki render će imati svoju "verziju" tog stanja.
Sa druge strane, useEffect je udica dizajnirana za nuspojave.: kod koji se izvršava nakon što je React iscrtao u DOM-u i koji komunicira s vanjskim sistemima: zahtjevi za podacima, pretplate na websocket, pristup localStorage, manipulacija prozorima, tajmeri sa setInterval o setTimeout, Itd
Iako se često kaže da je `useEffect` ekvivalentno kombinaciji `componentDidMount`, `componentDidUpdate` i `componentWillUnmount` , tačnije je smatrati da opisuje "šta treba sinhronizovati sa vanjskim svijetom nakon svakog renderovanja ". Niz zavisnosti zatim definiše kada se ta sinhronizacija odvija.

useState: deklarativno upravljanje lokalnim stanjem
Udica `useState` se deklariše na vrhu komponente i prima početnu vrijednost . Vraća niz sa dvije pozicije: trenutnom vrijednošću i funkcijom za njeno ažuriranje. Nešto jednostavno poput brojača se definiše ovako:
Osnovni primjer useState-a:
import { useState } from "react";
funkcija OsnovniBrojač() {
const[broj, postaviBroj] = koristiState(0);
konstantni prirast = () => {
postaviBroj(broj + 1);
};
povratak (
Pritisnuli ste dugme {count} puta
Pritisni me
);
}
U svakom renderu, broj predstavlja stanje koje odgovara tom specifičnom renderovanjuPrilikom poziva setCountReact zakazuje novo renderiranje s ažuriranom vrijednošću. Ako ažuriranje ovisi o prethodnoj vrijednosti, poželjno je koristiti funkcijski pristup:
setCount(prev => prev + 1);
Ova sintaksa izbjegava probleme kada je nekoliko ažuriranja povezano zajedno, jer React garantuje da je prev posljednja stabilna vrijednost , čak i u konkurentnom režimu.
useEffect: Razumijevanje njegovog životnog ciklusa i niza zavisnosti
Potpis efekta hook je vrlo jednostavan: useEffect(efecto, dependencias?)Prvi argument je funkcija podešavanja (setup), koja se izvršava nakon što je DOM iscrtan. Opcionalno, ta funkcija može vratiti drugu funkciju čišćenja koju će React pozvati prije ponovnog pokretanja efekta ili prilikom demontiranja komponente.
Ključ za efikasno korištenje useEffect-a je razumijevanje niza zavisnosti.Ovaj niz označava od kojih "reaktivnih" vrijednosti zavisi logika efekta: svojstva, stanja i varijable ili funkcije definirane unutar komponente. React upoređuje trenutne zavisnosti sa onima iz prethodnog renderovanja koristeći Object.isAko dođe do bilo kakve promjene, prvo pokrenite prethodno čišćenje, a zatim konfigurirajte novi efekat.
U zavisnosti od toga kako napišete taj niz, dobićete različita ponašanja:
- Nema drugog argumentaEfekat se izvršava nakon svaki render.
- Prazan niz []Efekat se pokreće automatski jednom nakon prvog renderiranja (režim „componentDidMount“ i „componentWillUnmount“).
- Niz sa zavisnostima [a, b]Efekat se izvršava nakon prvog renderovanja i svaki put kada se jedna od tih zavisnosti promijeni.
Vrlo tipičan primjer: Ažurirajte naslov dokumenta pomoću brojačaTo je moderni funkcionalni ekvivalent klasičnog obrasca klase sa componentDidMount + componentDidUpdate.
Primjer useEffect sinhronizacije naslova dokumenta:
import { useState, useEffect } from "react";
funkcija BrojačSNaslovom() {
const[broj, postaviBroj] = koristiState(0);
korištenjeEfekata(() => {
document.title = `Pritisnuli ste dugme ${count} puta`;
}, [broj]);
povratak (
Dugme je pritisnuto {count} puta
setCount(count + 1)}>Guraj me
);
}
U ovom slučaju, ovisnost je counti mora biti u nizu, bez obzira na sve.jer se koristi unutar efekta. Ako biste pokušali da ga izostavite, eslint-plugin-react-hooks Upozorilo bi vas; a ako biste prisilili linter da se utiša, završili biste s teško uočljivim greškama gdje naslov ne odgovara stvarnoj vrijednosti brojača.

Efekti koji nisu sanitizirajući: logika koja se mora izvršiti tek nakon renderiranja
Postoji kategorija efekata koji ne zahtijevaju eksplicitno čišćenje . To su efekti "uradi nešto i zaboravi na to" : prijavljivanje u konzolu, mijenjanje naslova dokumenta, upućivanje jednokratnog HTTP zahtjeva, mjerenje nečega samo jednom itd.
U svijetu društvenih klasa, ova logika je bila raspoređena među componentDidMount y componentDidUpdate, često s dupliciranim kodom. S hookovima, to dupliciranje nestaje jer Jedan useEffect može obraditi i početno renderiranje i ažuriranja..
Jednostavan primjer efekta bez sanitizacije: dobijanje lokacije korisnika pomoću API-ja za geolokaciju preglednika i spremanje iste u stanju:
import { useState, useEffect } from "react";
funkcija LokacijaKorisnika() {
const [latitude, setLatitude] = useState(0);
const [dužina, postaviDužina] = korištenjeStata(0);
korištenjeEfekata(() => {
ako (!navigator.geolokacija) {
console.log("Preglednik ne podržava geolokaciju");
povratak;
}
navigator.geolokacija.getCurrentPosition((pos) => {
postaviGeografskuŠirinu(poz.koordinate.geografskaŠirina.uFiksnu(0));
postaviDužinu(poz.koordinate.dužina.uFiksnu(0));
});
}, []);
U ovom slučaju, želimo zatražiti lokaciju samo jednom, kada se komponenta montira . Stoga je niz zavisnosti prazan: nema ničega što bi trebalo ponovo pokrenuti efekat. Nije potrebno čišćenje jer nismo ostavili nikakve aktivne intervale, pretplate ili slušače.
Još jedan uobičajeni primjer nesteriliziranog efekta je praćenje zapisnika ili analitike kada se određene informacije promijene , na primjer, bilježenje u kojoj se chat sobi korisnik nalazi ili koliko artikala ima u korpi, sve dok se ne održava živa veza povezana s tim događajem.
Efekti sanitacije: pretplate, intervali i curenje memorije
Druga glavna kategorija su efekti koji zahtijevaju čišćenje . To se obično dešava kada kreirate nešto što treba isključiti ili otkazati kada se promijene zavisnosti ili kada se komponenta demontira: API pretplate, globalni slušači događaja, tajmeri, web socketi itd.
Uzorak je uvijek isti: unutar efekta inicijalizirate resurs i vraćate funkciju koja ga poništava . React će pozvati tu funkciju čišćenja prije ponovnog pokretanja efekta i kada komponenta nestane iz DOM-a.
Klasičan primjer: brojač koji se automatski povećava u određenom intervalu.
import { useState, useEffect } from "react";
const AutomatskiBrojač = () => {
const [brojač, postaviBrojač] = korištenjeStata(0);
korištenjeEfekata(() => {
konstantni interval = postaviInterval(() => {
console.log("Interval...");
postaviBrojač((c) => c + 1);
}, 2000);
vratiti () => {
console.log("Interval brisanja");
obrišiInterval(interval);
};
}, []);
povratak {brojač} ;
};
Ovdje se interval kreira kada se komponenta montira, a briše kada se demontira. Ako zaboravite pozvati clearInterval, Interval bi nastavio teći čak i ako komponenta više nije prikazana.uzrokujući curenje memorije i ažuriranja stanja na rastavljenim komponentama.
Još jedan vrlo uobičajen obrazac je pretplata na globalne događaje, kako resize de window o keydown en document:
import { useEffect } from "react";
funkcija GlobalniDogađaji() {
korištenjeEfekata(() => {
const handleResize = () => {
console.log("Promjena veličine prozora");
};
const handleKeyDown = (događaj) => {
console.log(«Pritisnuta tipka», event.tipka);
};
window.addEventListener("promjena veličine", handleResize);
document.addEventListener("pritiskanatipka", rukovateljtipkom);
vratiti () => {
window.removeEventListener("promjena veličine", handleResize);
document.removeEventListener("pritisak tipke", rukovanje tipkomKljučaKljuča);
};
}, []);
povratak Osluškivanje globalnih događaja ;
}
Proces čišćenja ostavlja sistem tačno onakvim kakav je bio prije instalacije komponente . Ovo preslikavanje između podešavanja i čišćenja je ključno, posebno imajući u vidu da u striktnom režimu razvoja, React može pokrenuti dodatni ciklus podešavanja → čišćenja → podešavanja kako bi otkrio greške.
HTTP zahtjevi sa useEffect i useState: recept korak po korak
Jedna od najčešćih upotreba useEffect-a je učitavanje podataka iz API-ja kada je komponenta montirana i pohranjivanje rezultata u stanje pomoću useState-a . Postoji vrlo praktičan "recept" koji možete ponovo koristiti gotovo svaki put:
- Komponenta se pokreće u režimu "učitavanja" sa tipskim stanjem
isLoading. - Zahtjev se podnese unutar useEffect-a. odmah nakon što React oboji komponentu.
- Sačuvaj odgovor u državi i marka
isLoadingcomofalsekad je gotovo. - Renderiraj uvjetno rotirajući pokazivač ili poruka o učitavanju tokom
isLoadingmoretruei podatke kada budu spremni.
Puni primjer: aplikacija koja prikazuje nasumično štene koristeći Dog API.
import { useState, useEffect } from "react";
funkcija SlučajniDog() {
const[imageUrl, setImageUrl] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [greška, postaviGrešku] = korištenjeState(null);
korištenjeEfekata(() => {
const fetchDog = async() => {
pokušajte {
postaviUčitavaSe(true);
postaviGrešku(null);
const odgovor = čekaj dohvati(«https://dog.ceo/api/breeds/image/random»);
if (!response.ok) {
throw new Error(«HTTP greška» + response.status);
}
const data = čekaj odgovor.json();
setImageUrl(podaci.poruka);
} uhvatiti (greška) {
setError(poruka.greške);
} konačno {
postaviUčitavaSe(false);
}
};
dohvatiDog();
}, []);
ako (isLoading) {
povratak Punjenje… ;
}
if (greška) {
povratak Greška: {error} ;
}
povratak (
);
}
Imajte na umu da je verifikovano response.ok razlikovati tačan odgovor (kodovi 200-299) od HTTP grešakaFetch odbacuje obećanja samo zbog mrežnih kvarova, tako da ako ne provjerite... ok Možda mislite da je "sve u redu" čak i sa 500.
Isti ovaj obrazac se može prilagoditi za zahtjeve koji zavise od props-ova. Na primjer, ako želite prikazati podatke određenog korisnika na osnovu userId koji dolazi odozgo, koristili biste taj rekvizit kao zavisnost efekta od osvježi podatke svaki put kada se promijene.
Zahtjevi koji zavise od rekvizita i sigurno otkazivanje tokom demontaže
Kada URL ili identitet podataka koji se učitavaju zavisi od svojstva (na primjer, ponovo učitajte korisnika ako se promijeni props.userId), važno je:
- Ispravno navedite svojstvo kao zavisnost efekta.
- Izbjegavajte ažuriranja statusa ako je komponenta uklonjena prije nego što zahtjev završi.
Jednostavan obrazac za otkazivanje je korištenje interne zastavice:
import { useEffect, useState } from "react";
import axios from "axios";
funkcija Korisnik({korisničkiId}) {
const [korisnik, postaviKorisnika] = useState(null);
korištenjeEfekata(() => {
neka jeMounted = true;
const fetchData = async() => {
const rezultat = čekanje axios(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
ako (jeMountirano) {
postaviKorisnika(rezultat.podaci);
}
};
dohvatiPodatke();
vratiti () => {
jeMounted = false;
};
}, [korisničkiId]);
ako (!korisnik) {
povratak Punjenje… ;
}
povratak (
{korisničko.ime}
Email adresa: {user.email}
Telefon: {user.phone}
);
}
Dok Montirano more trueDozvoljeno je ažuriranje statusaKada se komponenta ukloni, efekat čišćenja postavlja tu zastavicu. falseČak i ako se obećanje ispuni kasnije, setUser Više se neće izvršavati. Postoje sofisticiranije alternative (AbortController, biblioteke podataka, itd.), ali ovaj ilustrativni obrazac jasno objašnjava ideju.
Logika ponovne upotrebe: više efekata u istoj komponenti
Jedna od velikih prednosti hook-ova je to što niste ograničeni na jedan useEffect po komponenti . U stvari, često je poželjnije podijeliti logiku na nekoliko specijaliziranih efekata nego sve to strpati u jedan ogromni.
Zamislite komponentu koja ima oboje računovođa, udaljeni podaci i možda pretplataU razredu biste završili s metodama životnog ciklusa punim miješanog koda: u componentDidMount Pokrenuli ste peticiju i potpisali je; u componentDidUpdate ručno ste provjerili šta se promijenilo; u componentWillUnmount Očistio/la si obje stvari.
Pomoću hook-ova možete koristiti useEffect za svaku „ideju“ ili odgovornost :
import { useState, useEffect } from "react";
const MojaKomponenta = () => {
const[broj, postaviBroj] = koristiState(0);
const [podaci, setData] = useState(null);
korištenjeEfekata(() => {
console.log(«Montirana komponenta»);
}, []);
korištenjeEfekata(() => {
console.log(`Broj se promijenio na ${count}`);
}, [broj]);
korištenjeEfekata(() => {
console.log("Podaci su se promijenili");
}, [podaci]);
povratak (
setCount((c) => c + 1)}>Kliknite ovdje
);
};
Na ovaj način, svaki efekat se fokusira samo na ono što treba , kod postaje čitljiviji i lakše je dodavati ili uklanjati ponašanje bez narušavanja ostatka. React će primijeniti sve efekte komponente redoslijedom kojim ih deklarišete.
Optimizirajte performanse: zavisnosti i efekti koji se ne ponavljaju prekomjerno
Kada efekat obavlja skup posao (API upiti, teški proračuni, ponovno renderiranje od strane treće strane...), Ne želite da se pokreće pri svakom ažuriranju komponente.U komponenti klase, ovo se često rješavalo ručnim poređenjima. if (prevProps.x !== this.props.x) unutra componentDidUpdate.
Sa kukama, Optimizacija je ugrađena u sam potpis useEffect-a. korištenjem niza zavisnosti. Na primjer, ako želite da se efekat aktivira samo kada se promijeni count, deklarišete to kao:
useEffect(() => {
console.log("Ha cambiado count");
}, [count]);
React će uporediti stari niz zavisnosti [valorAnteriorDeCount] sa novim [valorNuevoDeCount]. Ako su svi elementi isti, efekat se preskače.Ako postoji bilo kakva razlika, prethodni efekat se čisti (ako je bilo čišćenja) i konfiguracija se ponovo pokreće.
Slično tome, ako želite efekat koji se izvršava samo jednom da bi se nešto inicijaliziralo (i opcionalno obrisalo nakon deaktiviranja), koristite prazan niz . Ali budite oprezni: svojstva i stanje koje pročitate unutar efekta zadržat će svoje početne vrijednosti , tako da ovaj obrazac treba koristiti samo kada ste sigurni da vaš efekat ne zavisi od ikakvih promjena.
Da biste izbjegli propuste, toplo se preporučuje aktiviranje pravila exhaustive-deps de eslint-plugin-react-hooksOvo pravilo analizira kod efekta i upozorava vas ako nedostaju neke zavisnosti, predlažući odgovarajuću ispravku. To je vrlo efikasan način da se izbjegnu suptilne greške kada se koriste zastarjele vrijednosti stanja ili svojstva.
Tipični problemi s useEffectom i kako ih izbjeći
Rad s efektima je jednostavan u teoriji, ali u praksi postoji niz ponavljajućih grešaka koje uvijek treba imati na umu kako se ne bi iznova i iznova ponavljale.
Jedna od najpoznatijih je beskonačna petlja renderiranja . Do nje dolazi kada vaš efekat ažurira stanje koje je dio njegovih zavisnosti na način koji ne konvergira. Na primjer, ako imate:
useEffect(() => {
setCount(count + 1);
}, [count]);
U svakom renderu, kao count Mijenja se, efekat se ponovo pokreće, a vi ponovo ažurirate stanje i tako dalje u nedogled. U ovim slučajevima, ili ste na pogrešnom mjestu (to ažuriranje ne bi trebalo da se nalazi u efektu), ili vam je potrebno druge vrste logike, kao što je interval sa čišćenjem, ili koristite funkcionalni oblik setCount(c => c + 1) bez stavljanja count u prostorijama.
Drugi problem je osjećaj da se efekat "postavke" "pokreće dvaput" prilikom setup-a . U striktnom režimu, React u razvoju izvodi neku vrstu "stres testa" pokretanjem setup → cleanup → setup odmah na početku. Ovo se ne dešava u produkciji, ali ističe loše dizajnirane efekte koji se ne čiste kako treba . Ako vaša logika čišćenja odražava vašu logiku postavljanja, korisnik ne bi trebao primijetiti ništa neobično.
Također je uobičajeno vidjeti efekte koji se ponavljaju u svakom renderiranju uprkos tome što postoje zavisnosti. To se obično dešava zato što Niz uključuje objekte ili funkcije kreirane u svakom renderiranjuPošto se reference uvijek mijenjaju, React pretpostavlja da se i zavisnost promijenila. Rješenje: deklarisati te funkcije ili objekte unutar samog efekta ili ih zapamtite pomoću useCallback/useMemo ako su zaista potrebni vani.
useEffect, eksterni sistemi i napredna upotreba sa efektima događaja
Svrha useEffect-a je da vaša komponenta bude sinhronizovana sa eksternim sistemima : socketima, mapama, ugrađenim video plejerima, API-jima pretraživača itd. Uzorak se uvijek zasniva na ideji "kada se ove zavisnosti promijene, ponovo se povežite sa novim vrijednostima i isključite se sa starim".
Na primjer, komponenta ChatRoom koji se povezuje s određenom prostorijom ovisno o serverUrl y roomId trebalo bi:
- Povežite se tokom vožnje s prvom kombinacijom
serverUrlyroomId. - Isključite se iz prethodne sobe i povežite se s novom ako korisnik promijeni sobe ili servere.
- Potpuno odspojite komponentu prilikom rastavljanja.
Sa useEffect i dobrim čišćenjem, osiguravate da svaki render ima svoju konzistentnu vezu i da React može ponoviti sekvencu podešavanja/čišćenja bez da išta ostane neispravno.
U naprednijim scenarijima, možda će vam trebati Pročitajte najnovije stanje i svojstva unutar efekta bez ponovnog pokretanja izvršavanjaTu dolaze do izražaja ovakvi obrasci. Efekti događaja (kao udice) useEffectEvent u novim preporukama), koji vam omogućavaju da uklonite određeni nereaktivni kod sa liste zavisnosti. U međuvremenu, praktičan pristup je koristite prilagođene reference ili hooke za one dijelove gdje klasično reaktivno ponašanje ne odgovara dobro.
U kontekstu renderiranja na strani servera (SSR), imajte na umu da efekti se aktiviraju samo na klijentuPočetni HTML se renderira na serveru, a kada se aplikacija hidrira u pregledniku, aktiviraju se useEffect naredbe. Ako trebate prikazati nešto drugačije samo na strani klijenta, možete koristiti stanje poput didMount to prolazi false a true u suštini sa [] i odatle uvjetovati renderiranje.
Savladavanje `useState` i `useEffect` u konačnici se svodi na internaliziranje činjenice da je svako renderiranje snimak vaše komponente, da su hookovi vezani za redoslijed kojim ih deklarirate i da efekti opisuju kako sinhronizirati taj snimak s vanjskim svijetom, uključujući i kako poništiti ono što treba poništiti. Uz dobro upravljanje ovisnostima, čišćenje i odvajanje efekata po odgovornostima, vaše komponente će funkcionirati mnogo predvidljivije, trošit će manje resursa i bit će znatno lakše zaključiti što bi moglo biti pogrešno kada se nešto ne ponaša kako treba.