Povećavanjem aplikacije, važno je da budete svesni kako vam je state organizovan i kako podaci teku kroz komponente. Suvišan ili dupliran state je čest uzrok bug-ova. U ovom poglavlju, naučićete kako da pravilno strukturirate state, kako da logika ažuriranja state-a bude održiva i kako da delite state između udaljenih komponenata.
U ovom poglavlju:
- Kako da razmišljate o UI promenama kao promenama stanja
- Kako da pravilno strukturirate state
- Kako da “podignete state” da bi ga delili između komponenata
- Kako da kontrolišete da li se state čuva ili resetuje
- Kako da grupišete kompleksnu state logiku u funkciju
- Kako da prosledite informaciju bez “prop drilling-a”
- Kako da skalirate upravljanje state-a dok aplikacija raste
Reagovanje na input pomoću stanja
Sa React-om, nećete direktno u kodu menjati UI. Na primer, nećete pisati komande poput “onemogući dugme”, “omogući dugme”, “prikaži uspešnu poruku”, itd. Umesto toga, opisaćete kakav UI želite da vidite za različita vizuelna stanja vaše komponente (“inicijalno stanje”, “stanje pisanja”, “uspešno stanje”), a onda ćete pokrenuti promene stanja kao odgovor na korisnički input. Ovo je slično onome kako dizajneri razmišljaju o UI-u.
Ovde je forma za kviz napravljena pomoću React-a. Primetite kako koristi status
state promenljivu da odluči da li da omogući submit dugme i da li da prikaže uspešnu poruku.
import { useState } from 'react'; export default function Form() { const [answer, setAnswer] = useState(''); const [error, setError] = useState(null); const [status, setStatus] = useState('typing'); if (status === 'success') { return <h1>To je tačno!</h1> } async function handleSubmit(e) { e.preventDefault(); setStatus('submitting'); try { await submitForm(answer); setStatus('success'); } catch (err) { setStatus('typing'); setError(err); } } function handleTextareaChange(e) { setAnswer(e.target.value); } return ( <> <h2>Kviz gradova</h2> <p> U kom gradu je bilbord koji pretvara vazduh u pijaću vodu? </p> <form onSubmit={handleSubmit}> <textarea value={answer} onChange={handleTextareaChange} disabled={status === 'submitting'} /> <br /> <button disabled={ answer.length === 0 || status === 'submitting' }> Submit </button> {error !== null && <p className="Error"> {error.message} </p> } </form> </> ); } function submitForm(answer) { // Pretvaraj se da koristiš mrežni poziv. return new Promise((resolve, reject) => { setTimeout(() => { let shouldError = answer.toLowerCase() !== 'lima' if (shouldError) { reject(new Error('Dobar pokušaj, ali pogrešan odgovor. Probaj ponovo!')); } else { resolve(); } }, 1500); }); }
Ready to learn this topic?
Pročitajte Reagovanje na input pomoću stanja da naučite kako da pristupite interakcijama iz perspektive stanja.
Read MoreOdabir strukture state-a
Pravilno strukturiranje state-a može napraviti razliku između komponente koju je lako menjati i debug-ovati, i one koja je stalan izvor bug-ova. Najbitniji princip je da state ne bi trebao da sadrži suvišne i duplirane informacije. Ako postoji nepotreban state, lako je zaboraviti ažurirati ga i time uvesti bug-ove!
Na primer, ova forma ima suvišnu fullName
state promenljivu:
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [fullName, setFullName] = useState(''); function handleFirstNameChange(e) { setFirstName(e.target.value); setFullName(e.target.value + ' ' + lastName); } function handleLastNameChange(e) { setLastName(e.target.value); setFullName(firstName + ' ' + e.target.value); } return ( <> <h2>Prijavite se</h2> <label> Ime:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Prezime:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Vaša karta će biti izdata na ime: <b>{fullName}</b> </p> </> ); }
Možete je ukloniti i pojednostaviti kod računanjem fullName
-a tokom renderovanja komponente:
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const fullName = firstName + ' ' + lastName; function handleFirstNameChange(e) { setFirstName(e.target.value); } function handleLastNameChange(e) { setLastName(e.target.value); } return ( <> <h2>Prijavite se</h2> <label> Ime:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Prezime:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Vaša karta će biti izdata na ime: <b>{fullName}</b> </p> </> ); }
Ovo možda deluje kao mala izmena, ali dosta bug-ova u React aplikacijama se popravljaju na ovaj način.
Ready to learn this topic?
Pročitajte Odabir strukture state-a da naučite kako dizajnirati state da izbegnete bug-ove.
Read MoreDeljenje 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.
U ovom primeru, samo jedan panel treba biti aktivan. Da biste to postigli, umesto čuvanja state-a u svakom pojedinačnom panel-u, roditeljska komponenta drži state i specificira props svoje dece.
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> ); }
Ready to learn this topic?
Pročitajte Deljenje state-a između komponenata da naučite kako da podignete state i sinhronizujete komponente.
Read MoreČuvanje i resetovanje state-a
Kada ponovo renderujete komponentu, React mora da odluči koje delove stabla da zadrži (i ažurira), a koje da odbaci ili ponovo kreira od nule. U većini slučajeva, React-ovo automatsko ponašanje radi dovoljno dobro. Po default-u, React čuva delove stabla koji se “podudaraju” sa prethodno renderovanim stablom komponente.
Međutim, ponekad ovo nije ono što želite. U ovoj aplikaciji za poruke, pisanje poruke i naknadna promena primaoca ne resetuju input. Ovo može navesti korisnika da greškom pošalje poruku pogrešnoj osobi:
import { useState } from 'react'; import Chat from './Chat.js'; import ContactList from './ContactList.js'; export default function Messenger() { const [to, setTo] = useState(contacts[0]); return ( <div> <ContactList contacts={contacts} selectedContact={to} onSelect={contact => setTo(contact)} /> <Chat contact={to} /> </div> ) } const contacts = [ { name: 'Taylor', email: 'taylor@mail.com' }, { name: 'Alice', email: 'alice@mail.com' }, { name: 'Bob', email: 'bob@mail.com' } ];
React vam omogućava da override-ujete default ponašanje i forsirate komponentu da resetuje svoje stanje prosleđivanjem različitog key
-a, na primer <Chat key={email} />
. Ovo govori React-u da ako je primalac drugačiji, treba da smatra da se drugačija Chat
komponenta treba ponovo kreirati od nule sa novim podacima (i UI-jem poput input-a). Sada, promena primaoca resetuje polje za input—iako renderujete istu komponentu.
import { useState } from 'react'; import Chat from './Chat.js'; import ContactList from './ContactList.js'; export default function Messenger() { const [to, setTo] = useState(contacts[0]); return ( <div> <ContactList contacts={contacts} selectedContact={to} onSelect={contact => setTo(contact)} /> <Chat key={to.email} contact={to} /> </div> ) } const contacts = [ { name: 'Taylor', email: 'taylor@mail.com' }, { name: 'Alice', email: 'alice@mail.com' }, { name: 'Bob', email: 'bob@mail.com' } ];
Ready to learn this topic?
Pročitajte Čuvanje i resetovanje state-a da naučite više o životnom veku state-a i kako da ga kontrolišete.
Read MoreIzdvajanje state logike u reducer
Komponente sa mnogo ažuriranja state-a koji se prostiru kroz mnogo event handler-a mogu postati preobimne. U tim slučajevima, možete grupisati svu logiku ažuriranja state-a izvan komponente u jednu funkciju koja se naziva “reducer”. Vaši event handler-i postaju koncizni jer samo specificiraju korisničke “akcije”. Na dnu fajla, reducer funkcija specificira kako bi state trebao da se ažurira kao reakcija na svaku akciju!
import { useReducer } from 'react'; import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; export default function TaskApp() { const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); function handleAddTask(text) { dispatch({ type: 'added', id: nextId++, text: text, }); } function handleChangeTask(task) { dispatch({ type: 'changed', task: task }); } function handleDeleteTask(taskId) { dispatch({ type: 'deleted', id: taskId }); } return ( <> <h1>Plan puta u Pragu</h1> <AddTask onAddTask={handleAddTask} /> <TaskList tasks={tasks} onChangeTask={handleChangeTask} onDeleteTask={handleDeleteTask} /> </> ); } function tasksReducer(tasks, action) { switch (action.type) { case 'added': { return [...tasks, { id: action.id, text: action.text, done: false }]; } case 'changed': { return tasks.map(t => { if (t.id === action.task.id) { return action.task; } else { return t; } }); } case 'deleted': { return tasks.filter(t => t.id !== action.id); } default: { throw Error('Nepoznata akcija: ' + action.type); } } } let nextId = 3; const initialTasks = [ { id: 0, text: 'Poseti Kafkin muzej', done: true }, { id: 1, text: 'Gledaj lutkarsku predstavu', done: false }, { id: 2, text: 'Slikaj Lenonov zid', done: false } ];
Ready to learn this topic?
Pročitajte Izdvajanje state logike u reducer da naučite kako grupisati logiku u reducer funkciju.
Read MoreProsleđivanje podataka duboko uz context
Često ćete proslediti informaciju od roditeljske ka dečjoj komponenti kroz props. Ali, prosleđivanje props-a može postati nepogodno ako trebate proslediti neki prop kroz mnogo komponenata ili ako mnogo komponenata treba imati istu informaciju. Context omogućava roditeljskoj komponenti da neku informaciju učini dostupnom svakoj komponenti u stablu ispod nje—bez obzira koliko duboko—bez eksplicitnog prosleđivanja props-a.
Ovde, Heading
komponenta određuje nivo naslova “pitajući” najbliži Section
za svoj nivo. Svaki Section
prati svoj nivo pitajući roditeljski Section
i dodajući jedan na to. Svaki Section
pruža informaciju svim komponentama ispod bez prosleđivanja props-a—to radi kroz context.
import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return ( <Section> <Heading>Title</Heading> <Section> <Heading>Heading</Heading> <Heading>Heading</Heading> <Heading>Heading</Heading> <Section> <Heading>Sub-heading</Heading> <Heading>Sub-heading</Heading> <Heading>Sub-heading</Heading> <Section> <Heading>Sub-sub-heading</Heading> <Heading>Sub-sub-heading</Heading> <Heading>Sub-sub-heading</Heading> </Section> </Section> </Section> </Section> ); }
Ready to learn this topic?
Pročitajte Prosleđivanje podataka duboko uz context da naučite da koristite context kao alternativu prosleđivanju props-a.
Read MoreSkaliranje sa reducer-om i context-om
Reducer-i omogućavaju grupisanje logike za ažuriranje state-a u komponenti. Context vam omogućava da prosledite informaciju duboko drugim komponentama. Možete kombinovati reducer-e i context da upravljate složenim state-om.
Sa ovim pristupom, roditeljska komponenta upravlja kompleksnim state-om pomoću reducer-a. Druge komponente negde duboko u stablu mogu čitati njen state preko context-a. One takođe mogu otpremiti akcije koje ažuriraju taj state.
import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; import { TasksProvider } from './TasksContext.js'; export default function TaskApp() { return ( <TasksProvider> <h1>Slobodan dan u Kjotu</h1> <AddTask /> <TaskList /> </TasksProvider> ); }
Ready to learn this topic?
Pročitajte Skaliranje sa reducer-om i context-om da naučite kako se upravljanje state-om povećava u rastućoj aplikaciji.
Read MoreŠta je sledeće?
Pređite na Reagovanje na input pomoću stanja da biste počeli da čitate ovo poglavlje stranicu po stranicu!
Ili, ako ste već upoznati sa ovim temama, zašto ne biste pročitali poglavlje Evakuacioni izlazi?