- React stanje mora se tretirati kao nepromjenjivo, s ažuriranjima koja se vrše putem settera, a ne direktnim mutacijama, posebno za objekte i nizove.
- Ažuriranja stanja su asinhrona i mogu se grupno ažurirati, tako da korištenje funkcionalnih ažurirača izbjegava probleme sa zastarjelim stanjem u tajmerima, zatvaranjima i brzim interakcijama.
- Funkcijske komponente s Hookovima (useState, useRef i prijatelji) su moderni standard, dok alati poput React.memo i Immer pomažu s performansama i ugniježđenim podacima.
- Jasno razdvajanje svojstava i stanja, plus model toka podataka od vrha prema dolje, održava ponašanje komponenti predvidljivim kako se aplikacije skaliraju.
Stanje je jedan od onih React koncepata koji na površini izgledaju jednostavno, ali brzo postaju komplikovani kako vaša aplikacija raste. Počinjete s malim brojačem, a onda odjednom žonglirate s više polja obrasca, asinhronim ažuriranjima, ugniježđenim objektima i problemima s performansama kada se sve istovremeno renderira. Dubinsko razumijevanje stanja je ono što razlikuje nekoga ko "koristi React" od nekoga ko može skalirati i debugirati React aplikacije iz stvarnog svijeta.
U ovom vodiču ćemo proći kroz trenutno stanje u Reactu (namjerna igra riječi), od komponenti klase i metoda životnog ciklusa do modernih Hookova i nepromjenjivih ažuriranja. Također ćemo se pozabaviti suptilnim, ali ključnim temama poput asinhronih ažuriranja, zastarjelih zatvaranja, kada koristiti useRef umjesto useState i kako održati predvidljivim korisnički interfejs. Cilj je da vam pružimo jasan mentalni model kako bi se vaše komponente ponašale tačno onako kako očekujete.
Od rekvizita do stanja: šta zapravo gdje pripada?
U srži svake React komponente postoje dva glavna izvora podataka: props i state. rekviziti prenose se iz roditeljske komponente i ostaju fiksni tokom životnog vijeka tog rendera, dok stanje u vlasništvu je i pod kontrolom same komponente i namijenjen je za podatke koji se mijenjaju tokom vremena.
Dobro pravilo je: ako su podaci konfigurirani izvana i ne mijenjaju se u ovoj komponenti, to je prop; ako komponenta mora pratiti i ažurirati podatke, to je stanje. Zamislite trepćuću tekstualnu komponentu: stvarni tekst se unosi jednom (prop), ali se stanje (stanje) stalno mijenja. Ova razlika omogućava Reactu da protok podataka bude predvidljiv i jednosmjeran.
React potiče jednosmjerni (od vrha prema dolje) tok podataka gdje se stanje nalazi u najbližem zajedničkom pretku koji ga treba kontrolirati. Roditeljska komponenta može sadržavati stanje i prosljeđivati vrijednosti kao props-ove djeci, koja ih mogu renderirati ili transformirati, ali ne moraju znati da li te vrijednosti prvobitno dolaze iz stanja, drugih props-ova ili su fiksno kodirane.
Zbog toga ćete često čuti da je država „lokalna“ ili „enkapsulirana“. Samo komponenta koja posjeduje dio stanja može ga promijeniti, a svaki korisnički interfejs izveden iz tog stanja teče prema dolje kroz props. Možete slobodno kombinirati komponente sa stanjem i bez stanja (čiste) komponente, a to da li je nešto sa stanjem smatra se detaljem implementacije koji se može mijenjati tokom vremena.
Komponente klase: stanje i životni ciklus na starinski način
Prije Hooksa, jedini način korištenja metoda stanja i životnog ciklusa u Reactu bio je s komponentama ES6 klase. Iako se većina modernih aplikacija oslanja na funkcionalne komponente, i dalje ćete vidjeti (a ponekad i održavati) komponente klase u mnogim kodnim bazama, tako da je vrijedno razumjeti kako one funkcioniraju.
Za pretvaranje funkcionalne komponente kao što je jednostavna Clock u razred, slijedite nekoliko mehaničkih koraka. Kreirate klasu koja proširuje React.Component, dodajte a render() metodu, premjestite tijelo funkcije u render, zamijeniti props sa this.propsi obrišite originalnu funkciju. Sve dok React nastavlja renderirati <Clock /> u isti DOM čvor, ponovo koristi jednu instancu te klase.
Dodavanje lokalnog stanja klasi znači definiranje konstruktora i dodjeljivanje inicijalnog this.state objekat. Na primjer, možete premjestiti date vrijednost iz propsa u stanje dodavanjem konstruktora koji poziva super(props) i setovi this.state = { date: new Date() }, a zatim zamjenom bilo koje upotrebe this.props.date in render() sa this.state.dateZapamtite da u komponentama klase trebate direktno dodijeliti samo this.state unutar konstruktora.
Metode životnog ciklusa su posebne metode klase koje React poziva u određenim tačkama u životnom ciklusu komponente. Kada se komponenta prvi put ubaci u DOM (montira), React poziva componentDidMount()Kada se ukloni (demontira), React poziva componentWillUnmount()U klasičnom primjeru otkucavanja sata, postavljate tajmer u componentDidMount i očistite to componentWillUnmount, pohranjivanje ID-a tajmera na this (na primjer this.timerId) i pozivanje this.setState() svake sekunde da bi se ažuriralo vrijeme.
Tipičan životni ciklus tog sata izgleda ovako: React poziva konstruktor za inicijalizaciju stanja, a zatim render() da bi se kreirao DOM, onda componentDidMount() gdje pokrećete tajmer. Svaki put kada se tajmer aktivira, pozivate setState(), koji stavlja ažuriranje u red čekanja i pokreće render() s novim stanjem. Nakon što se komponenta ukloni, componentWillUnmount() briše tajmer kako ne biste gubili resurse.
Ispravno upravljanje stanjem u nastavi također znači poštovanje tri važna pravila o setState. Ne smiješ mutirati this.state direktno, morate imati na umu da ažuriranja mogu biti asinhrona i grupna, te da biste trebali razumjeti da se ažuriranja plitko spajaju (spajaju se samo ključevi stanja najvišeg nivoa, a ne duboko ugniježđeni objekti).
Ispravno korištenje stanja: mutacije, asinhrona ažuriranja i protok podataka
Jedan od najvećih izvora zabune za početnike je taj što setState (i ekvivalent Hook-a) ne ažurira stanje odmah i nikada ne biste trebali mijenjati stanje objekata na mjestu. React često grupira više ažuriranja zajedno radi performansi, tako da oba this.state u klasama i varijablama stanja u Hook-ovima možda neće odražavati konačno stanje odmah nakon što zakažete ažuriranje.
Direktno mutirajuće stanje, kao što je to slučaj this.state.count++ ili mijenjanje svojstava objekta stanja, preskače React-ovo otkrivanje promjena i može uzrokovati da komponente ostanu zaglavljene na starim vrijednostima. React očekuje da tretirate svaki objekt u stanju samo za čitanje (read-only). Umjesto promjene postojećih objekata, kreirate novi objekt ili niz sa željenim promjenama i prosljeđujete ga ažuriraču stanja (state updater).
Budući da ažuriranja stanja mogu biti asinhrona, morate biti oprezni prilikom izračunavanja sljedećeg stanja iz prethodnog. U časovima, nešto poput this.setState({ count: this.state.count + 1 }) može biti pogrešno ako se više ažuriranja grupira u grupe. Rješenje je korištenje funkcionalnog oblika: this.setState((prevState, props) => ({ count: prevState.count + 1 }))Ovo garantuje da radite sa najnovijim snimkom stanja.
Isti obrazac postoji i sa Hook-ovima: možete pozvati ažuriranje sa funkcijom umjesto sa vrijednošću. Na primjer, setCount(prev => prev + 1) je sigurniji način za povećanje brojača ako nova vrijednost ovisi o prethodnoj ili ako se ažuriranja mogu dogoditi unutar tajmera ili rukovatelja događajima koji se pokreću kasnije.
Iako je stanje "lokalno", efekat promjene stanja se uvijek prenosi niz stablo komponenti. Ponovno renderiranje roditeljskog objekta pokrenuto ažuriranjem stanja također će po defaultu ponovo renderirati sve svoje potomke. Ovaj tok podataka od vrha prema dolje je fundamentalan za React-ov mentalni model: jedan izvor istine na vrhu, korisnički interfejs izveden iz njega ispod.
Moderni React: Hookovi i funkcionalne komponente
Od Reacta 16.8, Hookovi su postali standardni način za upravljanje stanjem i sporednim efektima u funkcijskim komponentama. Omogućavaju vam da koristite iste mogućnosti koje su imale komponente klase (i više) bez pisanja klasa ili rada sa... this i metode životnog ciklusa eksplicitno, apoyándose en el estado estable de JavaScript moderno.
Funkcijske komponente su sada podrazumijevani stil u React kodnim bazama. Umjesto pisanja class Example extends React.Component, definirate običnu funkciju kao što je function Example() { return <div />; }Kada vam je potrebno stanje, sporedni efekti ili reference, "povezujete" se s Reactom putem funkcija poput useState, useEffect i useRefHookovi se ne mogu koristiti unutar klasa i moraju poštovati Pravila za hookove (uvijek ih pozivajte na najvišem nivou vaše komponente, nikada u petljama ili uslovima).
The useState Hook je najjednostavniji način za dodavanje lokalnog stanja funkcijskoj komponenti. Uzima početnu vrijednost kao argument i vraća par: trenutnu vrijednost stanja i setter. Zahvaljujući destrukturiranju nizova, obično pišete nešto poput const = useState(0)React čuva ovo stanje između ponovnog renderiranja, što znači da se funkcija može pozvati više puta, ali se vrijednost stanja pamti.
Za razliku od klasnog stanja, vrijednost koju zadržavate u useState ne mora biti objekat. Možete pohraniti brojeve, stringove, logičke vrijednosti, nizove ili objekte - šta god odgovara podacima. Ako vam je potrebno više nezavisnih vrijednosti, možete pozvati useState nekoliko puta (na primjer, age, fruit, todos). Alternativno, možete pohraniti jedan objekt i upravljati više svojstava unutar njega, ali morate poštovati pravila nepromjenjivosti prilikom ažuriranja.
Kada pozovete funkciju setter koju vraća useState, ne mijenjate vrijednost sinhrono; stavljate ažuriranje u red čekanja baš kao i sa setState u razredima. Prilikom sljedećeg renderiranja, React daje vašoj komponenti novu vrijednost stanja. Zato će čitanje stanja odmah nakon pozivanja settera unutar iste sinhrone funkcije i dalje vratiti staru vrijednost.
Upravljanje objektima i ugniježđenim podacima u stanju
React vam omogućava da bilo koju JavaScript vrijednost stavite u stanje, uključujući objekte i nizove, ali ih morate tretirati kao nepromjenjive snimke stanja (snapshots). Primitivne vrijednosti poput brojeva i stringova se ionako ne mogu mutirati, ali objekti i nizovi tehnički mogu - međutim, njihova mutacija krši Reactove pretpostavke i može dovesti do suptilnih grešaka gdje se komponente ne ažuriraju.
Razmotrimo objekt stanja kao što je { x: 0, y: 0 } predstavlja poziciju pokazivača. Ako pišeš position.x = event.clientX direktno, mutirali ste postojeći objekat. React nema pojma da se vrijednost promijenila jer nikada niste pozvali setter, tako da se neće ponovo renderovati i vaš UI ostaje zaglavljen. Ispravan pristup je setPosition({ x: event.clientX, y: event.clientY }), što kreira potpuno novi objekt i govori Reactu da ga renderira.
Lokalna mutacija svježe kreiranih objekata je sasvim u redu. Na primjer, možete korak po korak izgraditi novi objekat: const next = { ...prev }; next.city = 'Paris'; sve dok next nije već bio u stanju. Mutacija postaje problem samo kada promijenite objekt koji se već koristi u nekom prethodnom snimku stanja jer se drugi dijelovi vaše aplikacije i dalje mogu oslanjati na tu staru vrijednost.
Da biste ažurirali samo dio objekta, a zadržali ostatak, obično koristite sintaksu širenja objekta. Za objekat stanja forme kao što je { firstName, lastName, email }, promjene unosa možete obraditi nečim poput setPerson({ ...person, : event.target.value })Ovo kopira stara svojstva, a zatim prepisuje samo ono koje se promijenilo. Rasprostranjenost je plitka, tako da ugniježđeni objekti zahtijevaju više pažnje.
Duboko ugniježđeni objekti mogu brzo dovesti do opširnog koda za ažuriranje, jer morate kreirati nove kopije duž svakog nivoa putanje koju mijenjate. Na primjer, ako person.artwork.city promjene, uradili biste setPerson({ ...person, artwork: { ...person.artwork, city: 'London' } })Ispod haube, ne postoji "ugniježđeni objekt"; postoje odvojeni objekti koji pokazuju jedan na drugi, tako da ako više roditelja pokazuje na isti podređeni objekt i vi ga mutirate, mijenjate podatke na više mjesta odjednom.
Ako stalno pišete ugniježđene spreadove, mogli biste razmisliti o izravnavanju oblika stanja ili korištenju pomoćne biblioteke poput Immera. Immer vam omogućava da pišete kod koji izgleda mutativan (kao draft.artwork.city = 'London') dok za vas kreira novu nepromjenjivu kopiju iza kulisa. U Reactu možete upariti Immer sa Hookovima putem useImmer iz use-immer Paket.
Stanje u praksi: obrasci, tajmeri i korisnički unos
U stvarnim aplikacijama rijetko upravljate stanjem samo za brojače; upravljate korisničkim unosom, API odgovorima i UI "režimima" poput učitavanja, greške i uspjeha. Ključna promjena načina razmišljanja s Reactom je da ne "manipulirate DOM-om" (na primjer, "onemogućite ovo dugme"); umjesto toga, opisujete kako bi korisnički interfejs trebao izgledati za svako stanje, a zatim ažurirate stanje.
Na primjer, komponenta kviza ili obrasca može pratiti status stanje koje se prebacuje između 'typing', 'submitting' i 'success'. JSX uslovno onemogućava dugme za slanje prilikom slanja i prikazuje poruku o uspjehu kada je odgovor tačan. Nikada ne pozivate imperativne DOM metode - React jednostavno ponovo renderuje s novim stanjem i vizuelni izlaz se mijenja.
Rukovanje poljima obrasca je prvo mjesto gdje se mnogi programeri susreću s razlikom između spajanja stanja klasa i useState ponašanje. U razredu, setState spaja objekt koji prosljeđujete u postojeći objekt stanja, tako da ažuriranje jednog polja ne uklanja ostala. Sa useState, ažuriranja zamjenjuju cijelu vrijednost: ako je vaše stanje objekt i pozovete setState({ email: '...' }), bilo koja druga svojstva (kao što password) nestaju osim ako ih ručno ne spojite.
Ova razlika sapliće ljude kada refaktorišu iz više primitivnih varijabli stanja u jedan objekat. Ako se promijenite iz const i const to const a zatim napišite generički setForm({ : value }), završit ćete sa objektom stanja koji uvijek ima samo jedno polje. Rješenje je proširivanje prethodnog objekta: setForm({ ...form, : value }).
U složenijim aplikacijama, često nećete pozivati setState (ili setSomething) direktno odasvud. Možete centralizirati stanje koristeći biblioteke poput Reduxa ili MobX-a ili koristiti useReducer Uključivanje za mašine stanja na nivou komponenti. U ovim postavkama i dalje se primjenjuju isti principi nepromjenjivosti; jedina razlika je gdje i kako se ažuriranja izvršavaju.
Ponovno renderiranje, performanse i kada koristiti useRef
Svako ažuriranje stanja u Reactu pokreće ponovno renderiranje komponente koja posjeduje stanje i, prema zadanim postavkama, sve njene potomke. Ovo je namjerno: ponovno renderiranje je način na koji vaš korisnički interfejs ostaje sinhronizovan sa trenutnim podacima. Ali to također znači da nepromišljeno postavljanje stanja može uzrokovati nepotreban rad i spore korisničke interfejse, posebno kada podređene komponente vrše skupe proračune ili renderuju velike liste.
Zamislite aplikaciju s poljem za unos i zasebnom komponentom koja prikazuje dugu listu vještina. Ako roditeljska komponenta posjeduje i tekst koji korisnik kuca i samu listu, onda će svaki pritisak tipke ponovo prikazati cijelo stablo, uključujući listu vještina, čak i ako se ta lista nije promijenila. To je uzaludan trud.
Jedan jednostavan način za optimizaciju ovoga je umotavanje podređenih komponenti u React.memo. React.memo je komponenta višeg reda koja memoriše rezultat funkcionalne komponente: ako su njeni propsi isti između renderovanja, React preskače ponovno renderovanje. Dakle, vaša komponenta liste vještina, jednom umotana u React.memo, neće se ponovo renderirati sa svakim pritiskom na tipku - samo kada je skills svojstvo se zapravo mijenja (na primjer, kada dodate novu vještinu).
Ne pripadaju svi podaci "slični stanju" u useStateponekad useRef je bolji alat. The useRef Hook vam daje promjenjivi objekt sa current svojstvo koje traje tokom cijelog životnog vijeka komponente, ali njegovo ažuriranje to čini ne pokrenuti ponovno renderiranje. To ga čini savršenim za pohranjivanje stvari poput ID-ova tajmera, referenci DOM elemenata ili brojača koje želite pratiti, ali ne moraju se prikazivati u korisničkom interfejsu.
Jednostavan primjer je brojač implementiran sa useRef umjesto useState. Ako pohranite brojač u countRef.current i povećate ga u obrađivaču događaja, interna vrijednost se mijenja, ali prikazani JSX se neće ažurirati jer React nije ponovo renderirao. Ovo ilustruje ključnu razliku: useState je za vrijednosti koje pokreću korisnički interfejs; useRef je za vrijednosti koje želite zadržati bez utjecaja na renderiranje.
Nepromjenjivost i zašto je direktna mutacija zamka
Osnovni princip u Reactu je da ažuriranja stanja moraju biti nepromjenjiva. To ne znači da nikada ne možete ništa promijeniti; to znači da umjesto modificiranja postojećih vrijednosti (posebno objekata i nizova), kreirate nove, a stare ostavljate kao historijske snimke vašeg korisničkog interfejsa.
Direktna promjena stanja prekida vezu između vašeg mentalnog modela i onoga što React radi. Ako uradite nešto slično state.count++ ili direktno u niz stanja, React neće znati da se išta promijenilo jer nikada niste pozvali funkciju updater. Interni snimak koji React koristi za odlučivanje kada ponovo renderirati ostaje isti, dok vaš kod misli da se vrijednost promijenila. Tako dobijate greške koje se "same ispravljaju" kada ponovo učitate sistem.
Također trebate izbjegavati dodjeljivanje vrijednosti stanja drugoj varijabli, a zatim mijenjanje te varijable. Na primjer, radeći const newCount = count; newCount++; i dalje mutira istu osnovnu vrijednost za primitive i za objekte, const copy = stateObj; uopće ne kreira kopiju - samo kreira još jednu referencu na isti objekt. Pravilno kopiranje zahtijeva obrasce poput { ...stateObj } za predmete ili za nizove.
Biblioteke poput Reduxa, MobX-a (kada su konfigurisane za nepromjenjivost) ili Immera postoje dijelom da bi se nametnuli ili pojednostavili nepromjenjivi obrasci. Bez obzira da li koristite React-ove ugrađene Hookove ili biblioteku za upravljanje stanjem, zlatno pravilo važi: nikada ne mijenjajte postojeće stanje ako očekujete da će React prihvatiti promjenu i ponovo renderirati.
Asinhrona ažuriranja, grupiranje i zastarjelo stanje
Jedan suptilan, ali ključan detalj o React stanju je da su ažuriranja asinhrona i zakazana, te se ne primjenjuju odmah. Kada zoveš setState ili postavljač udica poput setCountReact "stavlja u red" ponovno renderiranje na neko vrijeme u budućnosti. Ne blokira vaš kod odmah da se ažurira i ponovo renderira, što omogućava Reactu da grupno izvršava više ažuriranja i održava performanse glatkim.
Ovaj model raspoređivanja znači da se ne možete osloniti na čitanje stanja odmah nakon pozivanja ažuriranja unutar istog sinhronog bloka. Vrijednost koju dobijete obično će biti stari snimak. Umjesto toga, trebali biste razmišljati o ažuriranju kao o zahtjevu: „sljedeći put kada renderirate, koristite ovu vrijednost (ili ovu funkciju transformacije)“.
Ovo je posebno važno kada ažurirate stanje na osnovu njegove trenutne vrijednosti unutar closures kao što je setTimeout ili povratne pozive pretplate. Ti povratni pozivi bilježe stanje u trenutku kada su kreirani. Ako zatim to uradite setCount(count + 1) unutar timeouta, count na što se pozivate može biti zastarjelo do trenutka kada se povratni poziv zapravo pokrene.
Ovaj fenomen je poznat kao „ustajalo stanje“ ili „ustajala zatvaranja“. Na primjer, ako imate dugme koje, pri kliku, poziva funkciju koja postavlja vremensko ograničenje, a zatim povećava stanje nakon jedne sekunde, višestruki brzi klikovi možda neće ispravno povećati stanje. Svaki povratni poziv vremenskog ograničenja koristi stari count snimljeno je kada je zakazano isteklo vrijeme.
Robusno rješenje je korištenje funkcionalnog oblika ažuriranja vašeg setera stanja. Umjesto setCount(count + 1) unutar vremenskog ograničenja, pišete setCount(prevCount => prevCount + 1)Sada svaki povratni poziv prima najnoviju prethodnu vrijednost u trenutku kada se ažuriranje primijeni, a ne onu koja se slučajno nalazila u opsegu kada je timeout kreiran. Ovo eliminira problem zastarjelog stanja bez promjene ponašanja vaših closures-a na drugi način.
React-ova dokumentacija također ističe manje poznati detalj: ako vaš funkcionalni program za ažuriranje ne vraća ništa (undefined), React će preskočiti ponovno renderiranje. To znači da bi vaše funkcije ažuriranja uvijek trebale vratiti sljedeću vrijednost stanja (ili ponovo upotrijebiti prethodnu) osim ako eksplicitno ne želite spriječiti ažuriranje - nešto što je rijetko poželjno sa standardnim useState upotreba.
Razumijevanje ove kombinacije asinhronog raspoređivanja, grupiranja i ponašanja zatvaranja ključno je za pisanje pouzdane logike stanja u aplikacijama koje se bave vremenskim ograničenjima, intervalima, pretplatama ili brzim interakcijama korisnika. Kada shvatite da postavljači stanja zakazuju ažuriranja umjesto da ih izvršavaju odmah, greške koje su se prije činile nasumičnim počet će imati smisla.
Kada sve ove ideje spojite - props naspram stanja, životni ciklusi klasa naspram hookova, nepromjenjivost, kontrolirane komponente, useRef za nevizualne vrijednosti, memoizaciju, asinhrona ažuriranja i zastarjela zatvaranja - dobijate moćan, predvidljiv model za to kako se React korisnički interfejsi razvijaju tokom vremena. Umjesto razmišljanja u smislu imperativnih DOM promjena, dizajnirate modele s jasnim stanjem i puštate Reactu da se pobrine za ponovno renderiranje, što olakšava razmišljanje o vašim komponentama, njihovo testiranje i proširivanje kako vaša aplikacija raste.

