Deljenje state-a između komponenata
Ponekad želite da se state-ovi dve komponente menjaju zajedno. Da biste to uradili, uklonite state iz obe komponente, pomerite ga u najbližeg zajedničkog roditelja i prosledite ga nazad kroz props. Ovo je poznato kao podizanje state-a i jedna je od najčešćih stvari koje ćete pisati u React kodu.
Naučićete:
- Kako da podizanjem delite state između komponenata
- Šta su kontrolisane i nekontrolisane komponente
Podizanje state-a uz primer
U ovom primeru, roditeljska Accordion
komponenta renderuje dva različita Panel
-a:
Accordion
Panel
Panel
Svaka Panel
komponenta ima boolean isActive
state koji određuje da li je sadržaj vidljiv.
Pritisnite dugme “Prikaži” na oba panel-a:
import { useState } from 'react'; function Panel({ title, children }) { const [isActive, setIsActive] = useState(false); return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Prikaži </button> )} </section> ); } export default function Accordion() { return ( <> <h2>Almati, Kazahstan</h2> <Panel title="O gradu"> Sa populacijom od oko 2 miliona, Almati je najveći grad u Kazahstanu. Bio je glavni grad od 1929. do. 1997. godine. </Panel> <Panel title="Etimologija"> Ime potiče od reči <span lang="kk-KZ">алма</span>, što na kazaškom jeziku znači "jabuka", i često se prevodi kao "pun jabuka". U suštini, region koji okružuje Almati se smatra pradomovinom jabuka, a divlja <i lang="la">Malus sieversii</i> se smatra mogućim pretkom moderne domaće jabuke. </Panel> </> ); }
Primetite kako pritiskanje dugmeta u jednom panel-u ne utiče na drugi—oni su nezavisni.


Inicijalno, isActive
state svakog Panel
-a je false
, tako da su oba sklopljena


Klik na dugme bilo kog Panel
-a će ažurirati samo isActive
state tog Panel
-a
Ali, recimo da to želite promeniti tako da samo jedan panel može biti proširen. Sa takvim dizajnom, proširivanje drugog panel-a bi trebalo da sklopi prvi. Kako biste to uradili?
Da biste koordinisali ta dva panel-a, potrebno je da “podignete njihov state” u roditeljsku komponentu u ova tri koraka:
- Ukloniti state iz dečjih komponenata.
- Proslediti hardkodirane podatke iz zajedničkog roditelja.
- Dodati state u zajedničkog roditelja i proslediti ga zajedno sa event handler-ima.
Ovo će omogućiti Accordion
komponenti da koordiniše oba Panel
-a i proširi samo jedan.
Korak 1: Ukloniti state iz dečjih komponenata
Daćete kontrolu nad isActive
iz Panel
-a njegovoj roditeljskoj komponenti. Ovo znači da će roditeljska komponenta proslediti isActive
u Panel
kao prop. Počnite sa uklanjanjem ove linije iz Panel
komponente:
const [isActive, setIsActive] = useState(false);
I umesto toga, dodajte isActive
u listu props-a u Panel
-u:
function Panel({ title, children, isActive }) {
Sada roditeljska komponenta Panel
-a može kontrolisati isActive
prosleđivanjem prop-a. Sa druge strane, Panel
komponenta sada nema kontrolu nad vrednošću isActive
—sada je to na roditeljskoj komponenti!
Korak 2: Proslediti hardkodirane podatke iz zajedničkog roditelja
Da biste podigli state morate locirati najbližu zajedničku roditeljsku komponentu obe dečje komponente koje želite da koordinišete:
Accordion
(najbliži zajednički roditelj)Panel
Panel
U ovom primeru, to je Accordion
komponenta. Pošto je iznad oba panel-a i može da kontroliše njihove props-e, postaće “izvor istine” označavajući koji panel je trenutno aktivan. Napravite da Accordion
komponenta prosledi hardkodiranu vrednost za isActive
(na primer, true
) u oba panel-a:
import { useState } from 'react'; export default function Accordion() { return ( <> <h2>Almati, Kazahstan</h2> <Panel title="O gradu" isActive={true}> Sa populacijom od oko 2 miliona, Almati je najveći grad u Kazahstanu. Bio je glavni grad od 1929. do. 1997. godine. </Panel> <Panel title="Etimologija" isActive={true}> Ime potiče od reči <span lang="kk-KZ">алма</span>, što na kazaškom jeziku znači "jabuka", i često se prevodi kao "pun jabuka". U suštini, region koji okružuje Almati se smatra pradomovinom jabuka, a divlja <i lang="la">Malus sieversii</i> se smatra mogućim pretkom moderne domaće jabuke. </Panel> </> ); } function Panel({ title, children, isActive }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Prikaži </button> )} </section> ); }
Pokušajte da promenite hardkodirane isActive
vrednosti u Accordion
komponenti da vidite rezultat na ekranu.
Korak 3: Dodati state u zajedničkog roditelja
Podizanje state-a često menja prirodu onoga što čuvate kao state.
U ovom slučaju, samo jedan panel treba biti aktivan. To znači da zajednička roditeljska Accordion
komponenta treba da prati koji panel je aktivan. Umesto boolean
vrednosti može se koristiti broj kao indeks aktivnog Panel
-a u state promenljivoj:
const [activeIndex, setActiveIndex] = useState(0);
Kada je activeIndex
jednak 0
, prvi panel je aktivan, a kada je 1
, aktivan je drugi.
Klik na dugme “Prikaži” u bilo kom Panel
-u treba da promeni aktivan indeks u Accordion
-u. Panel
ne može da postavi activeIndex
state direktno pošto je on definisan unutar Accordion
-a. Accordion
komponenta mora da eksplicitno dozvoli Panel
komponenti da menja njen state prosleđivanjem event handler-a kao prop-a:
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>
<button>
unutar Panel
-a će sada koristiti onShow
prop kao svoj event handler za klik:
import { useState } from 'react'; export default function Accordion() { const [activeIndex, setActiveIndex] = useState(0); return ( <> <h2>Almati, Kazahstan</h2> <Panel title="O gradu" isActive={activeIndex === 0} onShow={() => setActiveIndex(0)} > Sa populacijom od oko 2 miliona, Almati je najveći grad u Kazahstanu. Bio je glavni grad od 1929. do. 1997. godine. </Panel> <Panel title="Etimologija" isActive={activeIndex === 1} onShow={() => setActiveIndex(1)} > Ime potiče od reči <span lang="kk-KZ">алма</span>, što na kazaškom jeziku znači "jabuka", i često se prevodi kao "pun jabuka". U suštini, region koji okružuje Almati se smatra pradomovinom jabuka, a divlja <i lang="la">Malus sieversii</i> se smatra mogućim pretkom moderne domaće jabuke. </Panel> </> ); } function Panel({ title, children, isActive, onShow }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={onShow}> Prikaži </button> )} </section> ); }
Ovim se zaokružuje podizanje state-a! Pomeranje state-a u zajedničku roditeljsku komponentu vam je omogućilo da koordinišete sa dva panel-a. Upotreba aktivnog indeksa umesto dva “da li je prikazano” flag-a osigurava da je samo jedan panel aktivan. Prosleđivanje event handler-a u dečje komponente je omogućilo deci da promene roditeljski state.


Inicijalno, Accordion
-ov activeIndex
je 0
, pa prvi Panel
prima isActive = true


Kada se activeIndex
state u Accordion
-u promeni na 1
, drugi panel Panel
prima isActive = true
umesto prvog
Deep Dive
Uobičajeno je komponente sa lokalnim state-om nazivati “nekontrolisanim”. Na primer, originalna Panel
komponenta sa isActive
state promenljivom je nekontrolisana jer njen roditelj ne može da utiče da li je panel aktivan ili ne.
Suprotno od toga, možete reći da je komponenta “kontrolisana” kada su bitne informacije u njoj vođene props-ima umesto njenim lokalnim state-om. Ovo omogućava roditeljskoj komponenti da potpuno specificira njeno ponašanje. Konačna Panel
komponenta sa isActive
prop-om je kontrolisana od strane Accordion
komponente.
Nekontrolisane komponente je lakše koristiti unutar njihovih roditelja jer zahtevaju manje konfiguracije. Ali, one su manje fleksibilne ako želite da ih koordinišete. Kontrolisane komponente su maksimalno fleksibilne, ali zahtevaju da ih roditeljske komponente u potpunosti konfigurišu preko props-a.
U praksi, “kontrolisano” i “nekontrolisano” nisu striktno tehnički termini—svaka komponenta obično ima neku kombinaciju lokalnog state-a i props-a. Međutim, ovo je koristan način da pričamo o tome kako su komponente dizajnirane i koje sposobnosti pružaju.
Kada god pišete komponentu, razmotrite koje informacije trebaju biti kontrolisane (kroz props), a koje informacije trebaju biti nekontrolisane (kroz state). Ali, uvek se možete predomisliti i refaktorisati kasnije.
Jedan izvor istine za svaki state
U React aplikaciji, mnoge komponente će imati svoj state. Poneki state može “živeti” u blizini listova (komponenti na dnu stabla) poput input-a. Drugi state može “živeti” bliže vrhu aplikacije. Na primer, čak su i biblioteke za rutiranje na klijentskoj strani često implementirane da čuvaju trenutnu rutu u React state-u, i da je prosleđuju deci kroz props!
Za svaki jedinstveni deo state-a, izabraćete komponentu koja ga “poseduje”. Ovaj princip je poznat i kao “jedan izvor istine”. To ne znači da svi state-ovi žive na jednom mestu—već da za svaki deo state-a postoji posebna komponenta koja drži tu informaciju. Umesto da duplirate deljeni state između komponenata, podignite ga u njihovog zajedničkog roditelja, koji će onda proslediti taj state deci kojima je potreban.
Vaša aplikacija će se menjati dok radite na njoj. Uobičajeno je da ćete pomerati state gore-dole dok pokušavate da shvatite gde svaki deo state-a “živi”. Sve je to deo procesa!
Da biste videli kako ovo izgleda u praksi sa nekoliko komponenata, pročitajte Razmišljanje u React-u.
Recap
- Kada želite da koordinišete dve komponente, pomerite njihov state u zajedničkog roditelja.
- Onda, prosledite tu informaciju iz zajedničkog roditelja kroz props.
- Na kraju, prosledite event handler-e kako bi deca mogla menjati roditeljski state.
- Korisno je smatrati komponente “kontrolisanim” (vođene props-ima) ili “nekontrolisanim” (vođene state-om).
Izazov 1 od 2: Sinhronizovani input-i
Ova dva input-a su nezavisna. Sinhronizujte ih: izmena jednog input-a treba da postavi isti tekst u drugi input, i obrnuto.
import { useState } from 'react'; export default function SyncedInputs() { return ( <> <Input label="Prvi input" /> <Input label="Drugi input" /> </> ); } function Input({ label }) { const [text, setText] = useState(''); function handleChange(e) { setText(e.target.value); } return ( <label> {label} {' '} <input value={text} onChange={handleChange} /> </label> ); }