- Razumeti, kako useState ohranja in posodablja stanje lokalnih komponent, vključno s funkcionalnimi posodobitvami in ravnanjem z objekti.
- Za stranske učinke uporabite useEffect z jasno logiko nastavitve/čiščenja in natančnimi polji odvisnosti, da se izognete puščanju in zankam.
- Združite useState in useEffect za naloge iz resničnega sveta, kot so pridobivanje podatkov, naročnine in posodobitve DOM v funkcijskih komponentah.
- Upoštevajte pravila kavljev in učinke obravnavajte kot procese "po upodabljanju", da bodo komponente Reacta predvidljive in vzdrževalne.
React hooks je popolnoma spremenil način pisanja komponent, in obvladovanje useState in useEffect je v bistvu vstopnica za pisanje sodobne kode React. Če jih že uporabljate, a se še vedno zatikate v neskončnih zankah, zastarelih stanjih ali zmedenih polji odvisnosti, vam bo ta vodnik pomagal povezati vse manjkajoče dele na praktičen način.
V tem članku bomo podrobneje preučili, kako pravilno uporabljati useState in useEffect skupaj, zakaj so bili kavlji sploh uvedeni, uradna pravila in opozorila, kako odvisnosti dejansko delujejo pod pokrovom, pogoste pasti, ki pokvarijo vaše komponente, in preizkušeni vzorci za stranske učinke, čiščenje in upravljanje stanja v resničnih projektih.
Zakaj kavlji in zakaj posebej useState in useEffect?
V Reactu 16.8 so bili dodani kavlji, ki omogočajo funkcijskim komponentam uporabo stanj in življenjskih ciklov brez razredov.Pred tem ste morali napisati komponente razreda, da bi ohranili lokalno stanje, se naročili na zunanje podatke ali se odzvali na dogodke življenjskega cikla, kot sta priklop in odklop.
Velika težava z razredi je bila, da je bila povezana logika pogosto razdeljena med več metod življenjskega cikla kot componentDidMount, componentDidUpdate in componentWillUnmountNa koncu bi imeli dele iste funkcije razpršene po različnih metodah, ki temeljijo na kdaj tečejo namesto kaj to počnejo, zaradi česar je kodo težje brati, testirati in ponovno uporabljati.
Kavlji obračajo ta model: s useState stanje neposredno pripnete funkcijski komponenti in z useEffect Stranske učinke pritrdite neposredno logiki, ki jih potrebuje. Na ta način lahko vse, kar je povezano z eno samo težavo, združite na enem mestu in pozneje preprosto izvlečete kavlje za večkratno uporabo.
Med vsemi kavlji, useState in useEffect so osnovni primitiviVečino vsakdanjih funkcij lahko zgradite samo s tema dvema: stanje uporabniškega vmesnika, kot so obrazci in preklopniki, omrežne zahteve, naročnine, časovniki, posodobitve DOM-a in drugo. Drugi kavlji (useRef, useReducer, useContext, useMemo...) so odlični, vendar gradijo na istih idejah.
Pravila React hookov, ki jih nikoli ne smete kršiti
React hooki imajo nekaj strogih pravil, zaradi katerih zanesljivo delujejo med upodabljanji.Če jih kršite, boste opazili bodisi napake med izvajanjem bodisi zelo subtilne, težko odpravljajoče napake.
Prvo pravilo: kavlje kličite samo znotraj komponent funkcij React ali kavljev po meriNe morete uporabljati useState or useEffect v komponentah razreda, običajnih uporabnih funkcijah ali zunaj katere koli komponente. Takšen vzorec ni veljaven:
import React, { Component, useState } from 'react';
class App extends Component {
// ❌ This will throw - hooks don’t work in classes
const = useState(0);
render() {
return <h1>Hello, I am a Class Component!</h1>;
}
}
Pravilen pristop je, da se premaknete na funkcijsko komponento, če želite uporabljati kavlje.:
import React, { useState } from 'react';
function App() {
const = useState('');
return (
<div>
Your JSX code goes in here...
</div>
);
}
export default App;
Drugo pravilo: kavlje kličite samo na najvišji ravni vaše komponenteTo pomeni, da ni nobenih kavljev znotraj zank, pogojev ali vgnezdenih funkcij. React se zanaša na klicanje kavljev v istem vrstnem redu pri vsakem upodabljanju, da se "ujema" vsak. useState in useEffect klic s shranjenimi podatki, zato je to neveljavno:
function BadComponent({ enabled }) {
if (enabled) {
// ❌ Wrong: hook inside a conditional
const = useState(0);
}
// ...
}
Namesto tega brezpogojno deklarirajte kavlje na vrhu in uporabite pogojne izraze znotraj učinka ali JSXKavelj je treba vedno poklicati, vendar je logika, ki jo izvaja, lahko pogojna:
function ConditionalEffectComponent() {
const = useState(false);
useEffect(() => {
if (isMounted) {
console.log('Component mounted');
}
}, );
return (
<div>
<button onClick={() => setIsMounted(!isMounted)}>
{isMounted ? 'Unmount' : 'Mount'}
</button>
</div>
);
}
Tretje implicitno pravilo je, da morajo biti kavlji uvoženi iz Reacta (ali knjižnice kavljev), ne pa implementirani ad-hoc.To je očitno, vendar je vredno povedati: čarovnija je v React-ovem notranjem dispečerju hook-ov, ki sledi klicem hook-ov med upodabljanji.
Pravilno upravljanje lokalnega stanja z useState
useState omogoča vam, da funkcijski komponenti pripnete stanje in prejmete tako trenutno vrednost kot funkcijo za posodabljanjeKonceptualno je to funkcionalni dvojnik this.state in this.setState v komponentah razreda.
Minimalni primer števca z useState izgleda takole:
import React, { useState } from 'react';
function Counter() {
const = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Counter;
Ko pokličete useState(initialValue), React shrani to stanje in vrne par: trenutna vrednost stanja in nastavitev. Za razliko od običajnih lokalnih spremenljivk se stanje ohrani med upodabljanji, zato count Vrednost se ne ponastavi na 0 vsakič, ko se funkcija komponente zažene.
Za stanje lahko uporabite katero koli serializirano vrednost: števila, nize, logične vrednosti, polja, objekte in celo funkcije. Lahko tudi pokličete useState večkrat v isti komponenti, da se povezane vrednosti ločijo, namesto da se vse stlači v en sam objekt.
Kadar je nova vrednost stanja odvisna od prejšnje, vedno uporabite obrazec za funkcionalno posodobitev.S tem se izognemo hroščem, ko se v hitrem zaporedju zgodi več posodobitev stanja:
setCount(prev => prev + 1);
Druga subtilna, a pomembna podrobnost je, da klic nastavitve nadomesti celotno vrednost stanja, ne pa združi objektov, kot je this.setState v razredihČe je vaše stanje objekt ali tabela, morate prejšnjo vrednost razširiti sami:
const = useState({ name: 'Alex', age: 30 });
// ✅ Correct: copy and update
setUser(prev => ({ ...prev, age: prev.age + 1 }));
Za drage začetne vrednosti lahko stanje lenobno inicializirate tako, da funkcijo posredujete useStateReact ga bo poklical samo pri prvem upodabljanju:
const = useState(() => calculateInitialValue());
Obvladovanje stranskih učinkov z useEffect
useEffect je React-ov API za izvajanje stranskih učinkov v funkcijskih komponentah»Stranski učinek« je vse, kar se dotika zunanjega sveta: pridobivanje podatkov, beleženje, neposredne spremembe DOM-a, naročnine, časovniki, API-ji brskalnika itd.
Konceptualno, useEffect nadomešča kombinacijo componentDidMount, componentDidUpdate in componentWillUnmount iz komponent razredaNamesto da en učinek razdelite na tri metode življenjskega cikla, ga deklarirate enkrat in pustite, da React obravnava, kdaj se izvaja in kdaj čisti.
Osnovni podpis je useEffect(setup, dependencies?). setup funkcija je telo vašega učinka; po želji lahko vrne funkcijo čiščenja. dependencies array pove Reactu, kdaj je treba učinek ponovno zagnati.
useEffect(() => {
// side effect logic here
return () => {
// optional cleanup logic here
};
}, );
Privzeto se bo učinek brez drugega argumenta zagnal po vsakem upodabljanju. (prva namestitev in vsaka nadaljnja posodobitev). To je pogosto preveč za omrežne zahteve ali drago logiko.
Zelo pogost vzorec je posodobitev nečesa zunanjega vsakič, ko se spremeni del stanja.Na primer, posodabljanje naslova strani glede na število klikov:
import React, { useState, useEffect } from 'react';
function Counter() {
const = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, ); // effect re-runs only when `count` changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}
Matrika odvisnosti je ključnega pomena za delovanje in pravilnostNadzoruje, kdaj naj React ponovno zažene učinek: če se je katera koli odvisnost spremenila glede na Object.is primerjava, učinek se počisti in znova zažene; če se nič ne spremeni, se preskoči.
Razumevanje matrike odvisnosti kot profesionalec
Najbolj subtilno je polje odvisnosti useEffect hrošči prihajajo izReact primerja vsak element tabele z njegovo prejšnjo vrednostjo z uporabo Object.isČe so vse vrednosti enake, se učinek preskoči; če je vsaj ena drugačna, se učinek ponovno izvede.
Ves čas boste uporabljali tri glavne konfiguracije odvisnosti:
- Brez drugega argumenta: učinek se izvede po vsakem upodabljanju.
- Prazna tabela
[]: učinek se izvede samo enkrat ob priklopu in se počisti ob odklopu. - Matrika z vrednostmi
: učinek se izvede po namestitvi in vsakič, ko se spremeni katera koli odvisnost.
Ko so odvisnosti primitivne vrednosti (števila, nizi, logične vrednosti), je to preprostoTežave se začnejo, ko v odvisnosti postavite objekte, polja ali funkcije, ker enakost temelji na referencah. Dva identična objekta z različnimi referencami se štejeta za "različna", kar povzroči ponovne zagone pri vsakem upodabljanju.
Razmislite o učinku, ki je odvisen od team predmet iz rekvizitov:
function Team({ team }) {
useEffect(() => {
console.log(team.id, team.active);
}, ); // ⚠️ might re-run every render if `team` reference changes
}
Tudi če se dejanska vsebina ekipe ne spremeni, bo nova referenca objekta pri vsakem upodabljanju prisilila učinek, da se ponovno zažene.Da bi se temu izognili, se bodisi zanašajte na primitivna polja, ki jih dejansko uporabljate, bodisi rekonstruirajte objekt znotraj samega učinka.
Varnejša različica sledi le tistemu, kar učinek resnično potrebuje:
function Team({ team }) {
const { id, active } = team;
useEffect(() => {
console.log(id, active);
}, );
}
Če resnično potrebujete celoten objekt znotraj učinka, ga lahko tam ponovno ustvarite, namesto da ga uporabljate kot odvisnost.Na ta način lahko seznam odvisnosti še vedno temelji na primitivih:
function Team({ team }) {
const { id, active, name } = team;
useEffect(() => {
const localTeam = { id, active, name };
// use `localTeam` here
}, );
}
Kot zadnjo možnost lahko uporabite memorizacijo z useMemo or useCallback za drage predmete ali funkcije, vendar ne pozabite, da ima sama pomnjenje svojo ceno. Ne posipajte ga povsod »za vsak slučaj«; dodajte ga, ko določena odvisnost resnično povzroča težave z zmogljivostjo.
Pravilno čiščenje učinkov
Nekateri stranski učinki dodelijo vire, ki jih je treba sprostiti: naročnine, vtičnice, intervali, časovne omejitve, poslušalci dogodkov itd. Če jih pozabite očistiti, lahko zlahka pride do puščanja pomnilnika ali podvajanja dela.
In useEffect, čiščenje se izvede z vrnitvijo funkcije iz učinkaReact bo poklical to funkcijo, preden ponovno zažene učinek z novimi odvisnostmi in tudi zadnjič, ko se komponenta odklopi.
import { useEffect } from 'react';
function LogMessage({ message }) {
useEffect(() => {
const log = setInterval(() => {
console.log(message);
}, 1000);
return () => {
clearInterval(log);
};
}, );
return <div>logging to console "{message}"</div>;
}
V tem primeru vsakič message spremembe, React najprej izbriše stari interval, nato pa nastavi novega s posodobljenim sporočilomKo komponenta izgine iz uporabniškega vmesnika, zadnje čiščenje dokončno počisti interval.
Ta par »nastavitev + čiščenje« je osrednjega pomena za miselni model useEffectPoskusite si vsak učinek predstavljati kot samostojen proces, ki se začne v funkciji nastavitve in se popolnoma ustavi v funkciji čiščenja. React lahko med razvojem (zlasti v strogem načinu) izvede več ciklov nastavitve/čiščenja, da preveri, ali čiščenje resnično razveljavi vse.
Klasični primer je naročanje na zunanji vir, kot je API za klepet ali dogodek brskalnika (glejte Obdelava onKeyDown v Reactu):
useEffect(() => {
function handleClick(event) {
console.log('Clicked', event.clientX, event.clientY);
}
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
};
}, []); // runs once on mount, cleans up on unmount
Uporaba useState in useEffect skupaj za pridobivanje podatkov
Ena najpogostejših kombinacij v resničnem svetu je uporaba useState in useEffect za pridobivanje podatkov iz API-jaPodatke (in morda zastavice nalaganja/napak) ohranite v stanju in zahtevo izvedete z učinkom, ki se zažene, ko se komponenta priklopi ali ko se spremeni kakšen parameter.
Osnovni vzorec za pridobivanje podatkov po priklopu je videti takole:
import { useEffect, useState } from 'react';
function FetchItems() {
const = useState([]);
useEffect(() => {
let ignore = false;
async function fetchItems() {
try {
const response = await fetch('/items');
const fetchedItems = await response.json();
if (!ignore) {
setItems(fetchedItems);
}
} catch (error) {
console.error('Error fetching items:', error);
}
}
fetchItems();
return () => {
// avoid updating state if the component unmounted
ignore = true;
};
}, []);
return (
<div>
{items.map(item => (
<div key={item.id ?? item}>{item.name ?? item}</div>
))}
</div>
);
}
Tukaj prazna matrika odvisnosti zagotavlja, da se zahteva izvede natanko enkratNotranji ignore Zastavica je preprost način, da se izognete nastavljanju stanja na odklopljeni komponenti, če se zahteva razreši pozno.
Prav tako je zelo pogosto dodati zastavico za nalaganje in prikazati vrtljivko ali nadomestno besedilo, medtem ko so podatki na poti.:
const Statistics = () => {
const = useState([]);
const = useState(true);
useEffect(() => {
const getStats = async () => {
try {
const statsData = await getData();
setStats(statsData);
} finally {
setLoading(false);
}
};
getStats();
}, []);
if (loading) {
return <div>Loading statistics...</div>;
}
return (
<ul>
{stats.map(stat => (
<li key={stat.id}>{stat.label}: {stat.value}</li>
))}
</ul>
);
};
Če je vaša poizvedba odvisna od parametra (kot je kategorija, filter ali parameter poti), dodajte ta parameter v polje odvisnosti. torej se učinek ponovi, ko se spremeni:
useEffect(() => {
async function fetchItems() {
const response = await fetch(`/items?category=${category}`);
const data = await response.json();
setItems(data);
}
fetchItems();
}, );
Razmišljanje o »učinkih na vsakem upodabljanju« v primerjavi z »življenjskimi cikli«
Če ste vajeni komponent razreda, je lahko skušnjava, da si jih miselno preslikate useEffect za priklop/posodobitev/odklop metod, vendar to običajno vodi do večje zmede. Preprostejši miselni model je: »učinki se izvajajo po upodabljanju in se lahko pred naslednjim zagonom očistijo«.
V razredih si pogosto moral podvajati logiko med componentDidMount in componentDidUpdate ker ste želeli, da se isti učinek izvaja tako pri priklopu kot pri posodobitvah. S kavlji ta podvojitev izgine: en sam učinek pokriva oba primera, React pa poskrbi za čiščenje med izvajanjem.
Ta zasnova odpravlja tudi celo vrsto napak, povezanih s nepravilnim obravnavanjem posodobitev.Na primer, v komponenti razreda, ki se naroči na spletni status prijatelja, je enostavno pozabiti na ponovno naročnino, ko props.friend spremembe, kar povzroča zastarele naročnine ali zrušitve ob odklopu. Z useEffect ki navaja friend.id Kot odvisnost bo React samodejno izvedel čiščenje za starega prijatelja in nastavitev za novega.
Ne pozabite, da v strogem načinu razvoja React namerno dvakrat zažene cikel nastavitve + čiščenja ob priklopu.To se v produkciji ne zgodi, vendar je uporaben preizkus obremenitve, s katerim potrdite, da čiščenje resnično razveljavi vse in da se lahko vaš učinek varno izvede večkrat.
Optimizacija in odpravljanje težav z vedenjem useEffect
Ko se učinek izvaja pogosteje, kot pričakujete, je treba najprej preveriti polje odvisnosti.Ali se odvisnost spremeni pri vsakem upodabljanju (pogosto pri vgrajenih objektih/funkcijah) ali pa ste sploh pozabili določiti polje.
Beleženje vrednosti odvisnosti je hiter način za odpravljanje napak:
useEffect(() => {
console.log('Effect deps:', dep1, dep2);
}, );
Če vsakič vidite različne dnevnike, preverite, katera odvisnost se dejansko spreminja.Pogosto boste pri vsakem upodabljanju ponovno ustvarili vgrajeni objekt ali funkcijo puščice. Premikanje ustvarjanja objektov znotraj učinka, dvigovanje funkcij zunaj komponente ali njihovo pomnjenje z useCallback lahko po potrebi stabilizira odvisnosti.
Neskončne zanke se pojavijo, ko je učinek odvisen od vrednosti in hkrati brezpogojno posodablja isto vrednost.. Na primer:
useEffect(() => {
setCount(count + 1); // ⚠️ will cause a loop if `count` is a dependency
}, );
Vsakič count spremembe, učinek se izvaja, posodobitve count znova sproži drugo upodabljanje in tako naprejDa bi prekinili ta vzorec, razmislite, ali posodobitev stanja resnično spada v učinek, ali naj jo sproži interakcija uporabnika ali pa se lahko zanesete na drugo vrednost.
Včasih želite prebrati najnovejšo vrednost nekega stanja ali lastnosti znotraj učinka, ne da bi ta vrednost sprožila ponovni zagon.V teh naprednih scenarijih se uporabljajo novejši API-ji, kot so »dogodki z učinki« (prek useEffectEvent (v dokumentaciji Reacta) ali reference lahko pomagajo, vendar je v večini praktičnih primerov varneje in preprosteje ostati zvest odvisnostim.
Sestavljanje vsega skupaj, uporaba useState in useEffect pravilno se zreducira na nekaj osnovnih navad: stanje naj bo majhno in osredotočeno, pri izpeljavi novega stanja iz starega dajte prednost funkcionalnim posodobitvam, učinke strukturirajte okoli parov nastavitev/čiščenje, bodite iskreni in eksplicitni z odvisnostnimi polji in vedno spoštujte pravila kavljev, da lahko React zanesljivo sledi, kaj kam spada. Ko upoštevate ta načela, vaše komponente ostanejo predvidljive, vaši stranski učinki se obnašajo pravilno, vaša kodna baza React pa se veliko lažje razvija z rastjo vaše aplikacije.
