Održavati komponente čistim
Neke JavaScript funkcije su čiste. Čiste funkcije izvršavaju samo proračune i ništa više. Pisanjem komponenata koje su striktno čiste funkcije, moći ćete da izbegnete zbunjujuće bug-ove u vašim klasama, iako se vaš projekat povećava. Da biste dobili te benefite, postoji par pravila koje morate ispoštovati.
Naučićete:
- Šta je zapravo čistoća i kako vam pomaže da izbegnete bug-ove
- Kako održati komponente čistim zadržavanjem promenena van faze renderovanja
- Kako da koristite Strict Mode da biste pronašli greške u komponentama
Čistoća: Komponente kao formule
U informatici (i pogotovo u svetu funkcionalnog programiranja), čista funkcija je funkcija koju odlikuju sledeće karakteristike:
- Gleda samo svoja posla. Ne menja nikakve objekte ili promenljive koji su postojali pre njenog poziva.
- Isti input-i, isti rezultat. Dobijanjem istih input-a, čista funkcija treba uvek da vrati isti rezultat.
Možda ste već upoznati sa jednim primerom čistih funkcija: formulama u matematici.
Pogledajte ovu matematičku formulu: y = 2x.
Ako je x = 2 onda je y = 4. Uvek.
Ako je x = 3 onda je y = 6. Uvek.
Ako je x = 3, y neće ponekad biti 9 ili –1 ili 2.5 u zavisnosti od trenutnog vremena ili stanja na berzi.
Ako je y = 2x i x = 3, y će uvek biti 6.
Ako prebacimo ovo u JavaScript funkciju, izgledalo bi ovako:
function double(number) {
return 2 * number;
}
U primeru iznad, double
je čista funkcija. Ako joj prosledite 3
, vratiće 6
. Uvek.
React je dizajniran oko ovog koncepta. React pretpostavlja da je svaka komponenta koju napišete čista funkcija. To znači da React komponente koje pišete uvek moraju vratiti isti JSX kada im prosledite iste input-e:
function Recipe({ drinkers }) { return ( <ol> <li>Skuvati {drinkers} časa sa vodom.</li> <li>Dodati {drinkers} kašika čaja i {0.5 * drinkers} kašika začina.</li> <li>Dodati {0.5 * drinkers} šolja mleka da provri i šećer po ukusu.</li> </ol> ); } export default function App() { return ( <section> <h1>Recept za začinjeni čaj</h1> <h2>Za dvoje</h2> <Recipe drinkers={2} /> <h2>Za okupljanje</h2> <Recipe drinkers={4} /> </section> ); }
Kada prosledite drinkers={2}
u Recipe
, on će vratiti JSX koji sadrži 2 časa sa vodom
. Uvek.
Ako prosledite drinkers={4}
, on će vratiti JSX koji sadrži 4 časa sa vodom
. Uvek.
Baš kao matematička formula.
Možete gledati vaše komponente kao recepte: ako ih pratite i ne dodajete nove sastojke tokom kuvanja, svaki put ćete dobiti isto jelo. To “jelo” je JSX koji će komponenta servirati React-u da renderuje.
Illustrated by Rachel Lee Nabors
Propratni efekti: (ne)namerne posledice
React-ov proces renderovanja mora uvek biti čist. Komponente bi trebale uvek da vrate svoj JSX, i da ne menjaju nikakve objekte ili promenljive koji su postojali pre renderovanja—to bi ih učinilo nečistim!
Evo komponente koja krši ovo pravilo:
let guest = 0; function Cup() { // Loše: menjanje već postojeće promenljive! guest = guest + 1; return <h2>Šolja čaja za gosta #{guest}</h2>; } export default function TeaSet() { return ( <> <Cup /> <Cup /> <Cup /> </> ); }
Ova komponenta čita i piše guest
promenljivu definisanu izvan nje. To znači da će pozivanje ove komponente više puta proizvesti različit JSX! I dodatno, ako druge komponente čitaju guest
, i one će proizvesti različit JSX takođe, u zavisnosti od toga kada su renderovane! To nije predvidljivo.
Vratimo se našoj formuli y = 2x. Sad iako je x = 2, ne možemo tvrditi da je y = 4. Naši testovi će biti neuspešni, korisnici pogubljeni, avioni će padati sa neba—vidite kako ovo može dovesti do zbunjujućih bug-ova!
Ovu komponentu možete popraviti prosleđivanjem guest
-a kao prop-a:
function Cup({ guest }) { return <h2>Šolja čaja za gosta #{guest}</h2>; } export default function TeaSet() { return ( <> <Cup guest={1} /> <Cup guest={2} /> <Cup guest={3} /> </> ); }
Sada je vaša komponenta čista jer JSX zavisi samo od guest
prop-a.
Uglavnom, ne trebate očekivati da se vaše komponente renderuju u određenom redosledu. Nije bitno da li y = 2x pozovete pre ili posle y = 5x: obe formule će se rešiti nevezano jedna od druge. Na isti način, svaka komponenta treba jedino da “misli na sebe”, a ne da pokušava da se usklađuje ili da zavisi od drugih tokom renderovanja. Renderovanje je nalik na test u školi: svaka komponenta treba da računa JSX samostalno!
Deep Dive
Iako ih možda niste sve koristili još uvek, u React-u postoje tri vrste input-a koje možete čitati tokom renderovanja: props, state i context. Ove input-e uvek trebate tretirati kao da su samo za čitanje.
Kada želite nešto promeniti u zavisnosti od korisničkog input-a, trebate setovati state umesto da pišete u promenljivu. Nikada ne bi trebalo menjati već postojeće promenljive ili objekte dok se vaša komponenta renderuje.
React nudi “Strict Mode” u kojem se dvaput pozivaju funkcije svake komponente u toku razvoja. Pozivanjem svake funkcije dvaput, Strict Mode pomaže u pronalasku komponenata koje krše ova pravila.
Primetite da je originalni primer prikazivao “Gost #2”, “Gost #4” i “Gost #6” umesto “Gost #1”, “Gost #2” i “Gost #3”. Originalna funkcija je bila nečista, pa ju je pozivanje dva puta pokvarilo. Popravljena, čista verzija radi čak iako se funkcija poziva stalno dva puta. Čiste funkcije samo računaju, pa pozivanje dva puta neće promeniti ništa—baš kao što pozivanje double(2)
dva puta ne menja povratnu vrednost, a ni rešavanje y = 2x dva puta ne menja koliko je y. Isti input-i, isti rezultati. Uvek.
Strict Mode nema efekta u produkciji, tako da neće usporiti vašu aplikaciju za korisnike. Da biste uključili Strict Mode, možete obmotati vašu root komponentu sa <React.StrictMode>
. Neki framework-ovi ovo rade po default-u.
Lokalna mutacija: Mala tajna vaše komponente
U primeru iznad, problem je bio u tome što je komponenta promenila već postojeću promenljivu tokom renderovanja. To se često zove “mutacija” kako bi zvučalo malko strašnije. Čista funkcija ne menja promenljive izvan opsega te funkcije niti objekte koji su kreirani pre njenog poziva—to je čini nečistom!
Međutim, potpuno je u redu menjati promenljive i objekte koje ste upravo kreirali tokom renderovanja. U ovom primeru, kreirate []
niz i dodeljujete ga u cups
promenljivu, a onda pozovete push
da dodate nekoliko čaša u taj niz:
function Cup({ guest }) { return <h2>Šolja čaja za gosta #{guest}</h2>; } export default function TeaGathering() { let cups = []; for (let i = 1; i <= 12; i++) { cups.push(<Cup key={i} guest={i} />); } return cups; }
Da su cups
promenljiva ili []
niz bili kreirani izvan TeaGathering
funkcije, ovo bi bio ogroman problem! Menjali bisti već postojeći objekat dodavanjem stavki u taj niz.
Međutim, u redu je jer ste ih kreirali u toku renderovanja, unutar TeaGathering
. Kod izvan TeaGathering
nikad neće saznati da se ovo desilo. To se zove “lokalna mutacija”—kao mala tajna vaše komponente.
Gde možete izazvati propratne efekte
Iako se funkcionalno programiranje zasniva na čistoći, u nekom trenutku, negde, nešto se mora promeniti. To je donekle svrha programiranja! Te promene—ažuriranje ekrana, početak animacije, promena podataka—se nazivaju propratni efekti. To su stvari koje se dešavaju “sa strane”, a ne tokom renderovanja.
U React-u, propratni efekti često pripadaju event handler-ima. Event handler-i su funkcije koje React pokreće kada izvršite neku akciju—na primer, kada kliknete dugme. Iako su event handler-i definisani unutar vaše komponente, oni se ne izvršavaju tokom renderovanja! Zbog toga event handler-i ne moraju biti čisti.
Ako ste iscrpeli sve ostale opcije i ne možete naći odgovarajući event handler za vaš propratni efekat, i dalje ga možete zakačiti u povratni JSX sa useEffect
pozivom u vašoj komponenti. Ovo govori React-u da izvrši nešto kasnije, nakon renderovanja, kada su propratni efekti dozvoljeni. Međutim, ovaj pristup bi trebao biti vaše poslednje rešenje.
Kada je moguće, pokušajte izraziti vašu logiku samo u toku renderovanja. Iznenadićete se koliko vas daleko to može dovesti!
Deep Dive
Pisanje čistih funkcija zahteva naviku i disciplinu. Takođe, otključava pregršt mogućnosti:
- Vaše komponente se mogu izvršavati u različitim sredinama—na primer, na serveru! Pošto vraćaju iste rezultate za iste input-e, jedna komponenta može opslužiti više korisničkih zahteva.
- Možete poboljšati performanse preskakanjem renderovanja komponenata čiji se input-i nisu promenili. Ovo je sigurno pošto čiste funkcije uvek vraćaju iste rezultate, pa je sigurno keširati ih.
- Ako se neki podatak promeni u toku renderovanja dubokog stabla komponenata, React može ponovo pokrenuti renderovanje bez da troši vreme na završetak zastarelog renderovanja. Čistoća čini bezbednim prekid proračuna u bilo kom trenutku.
Svaka nova React funkcionalnost koju pravimo koristi prednosti čistoće. Održavanje komponenata čistim otključava moć React paradigme, od fetch-ovanja podataka do animacija i performansi.
Recap
- Komponenta mora biti čista, što znači da:
- Gleda samo svoja posla. Ne bi trebalo da menja nikakve objekte ili promenljive koji su postojali pre renderovanja.
- Isti input-i, isti rezultat. Dobijanjem istih input-a, komponenta treba uvek da vrati isti JSX.
- Renderovanje se može desiti u bilo kom trenutku, pa komponente ne bi trebale da zavise od drugih.
- Ne bi trebali da menjate nijedan od input-a koje komponenta koristi za renderovanje. To uključuje props, state i context. Da biste ažurirali prikaz, “setujte” state umesto da menjate već postojeće objekte.
- Težite da izrazite logiku komponente u JSX-u koji vraćate. Kada trebate “menjati stvari”, uglavnom ćete želeti da to uradite u event handler-u. Kao poslednje rešenje, možete koristiti
useEffect
. - Pisanje čistih funkcija zahteva malo iskustva, ali otključava moć React paradigme.
Izazov 1 od 3: Popraviti pokvaren sat
Ova komponenta pokušava da postavi CSS klasu za <h1>
na "night"
između pomoći i šest ujutru, a na "day"
u svim ostalim slučajevima. Međutim, trenutno ne radi. Možete li popraviti ovu komponentu?
Možete proveriti da li vam rešenje radi privremenom promenom vremenske zone na računaru. Kad je vreme između ponoći i šest ujutru, sat bi trebao da ima obrnute boje!
export default function Clock({ time }) { let hours = time.getHours(); if (hours >= 0 && hours <= 6) { document.getElementById('time').className = 'night'; } else { document.getElementById('time').className = 'day'; } return ( <h1 id="time"> {time.toLocaleTimeString()} </h1> ); }