Õpi selgeks Reacti 'reconciliation' protsess. Saa teada, kuidas 'key' atribuudi õige kasutamine optimeerib nimekirjade renderdamist, ennetab vigu ja parandab rakenduse jõudlust.
Jõudluse avamine: sügavuti Reacti 'reconciliation' võtmetest nimekirjade optimeerimisel
Tänapäevases veebiarenduse maailmas on esmatähtis luua dünaamilisi kasutajaliideseid, mis reageerivad kiiresti andmete muutustele. React on oma komponendipõhise arhitektuuri ja deklaratiivse olemusega saanud ülemaailmseks standardiks nende liideste ehitamisel. Reacti tõhususe keskmes on protsess nimega reconciliation, mis hõlmab virtuaalset DOM-i. Kuid isegi kõige võimsamaid tööriistu saab kasutada ebatõhusalt ning levinud valdkond, kus nii uued kui ka kogenud arendajad komistavad, on nimekirjade renderdamine.
Olete tõenäoliselt lugematuid kordi kirjutanud koodi nagu data.map(item => <div>{item.name}</div>)
. See tundub lihtne, peaaegu triviaalne. Ometi peitub selle lihtsuse taga kriitiline jõudluse kaalutlus, mis ignoreerimisel võib viia aeglaste rakenduste ja segadust tekitavate vigadeni. Lahendus? Väike, kuid võimas atribuut: key
.
See põhjalik juhend viib teid sügavuti Reacti 'reconciliation' protsessi ja 'key' atribuutide asendamatusse rolli nimekirjade renderdamisel. Uurime mitte ainult 'mida', vaid ka 'miks' – miks võtmed on olulised, kuidas neid õigesti valida ja millised on valesti tegemise olulised tagajärjed. Lõpuks on teil teadmised, et kirjutada jõudlusvõimelisemaid, stabiilsemaid ja professionaalsemaid Reacti rakendusi.
1. peatükk: Reacti 'reconciliation' ja virtuaalse DOM-i mõistmine
Enne kui saame hinnata võtmete tähtsust, peame kõigepealt mõistma põhilist mehhanismi, mis teeb Reacti kiireks: 'reconciliation', mida toetab virtuaalne DOM (VDOM).
Mis on virtuaalne DOM?
Otse brauseri dokumendiobjekti mudeliga (DOM) suhtlemine on arvutuslikult kulukas. Iga kord, kui teete DOM-is muudatuse – näiteks lisate sõlme, uuendate teksti või muudate stiili –, peab brauser tegema märkimisväärsel hulgal tööd. See võib nõuda stiilide ja paigutuse ümberarvutamist terve lehe jaoks, protsess, mida tuntakse kui 'reflow' ja 'repaint'. Keerulises, andmepõhises rakenduses võivad sagedased otse DOM-i manipulatsioonid jõudluse kiiresti aeglustada.
React pakub selle lahendamiseks abstraktsioonikihi: virtuaalse DOM-i. VDOM on kerge, mälusisene esitus tegelikust DOM-ist. Mõelge sellest kui oma kasutajaliidese kavandist. Kui käsite Reactil kasutajaliidest uuendada (näiteks muutes komponendi olekut), ei puuduta React kohe tegelikku DOM-i. Selle asemel teostab see järgmised sammud:
- Luukase uus VDOM-puu, mis esindab uuendatud olekut.
- Seda uut VDOM-puud võrreldakse eelmise VDOM-puuga. Seda võrdlusprotsessi nimetatakse "diffing".
- React selgitab välja minimaalse hulga muudatusi, mis on vajalikud vana VDOM-i muutmiseks uueks.
- Need minimaalsed muudatused koondatakse seejärel kokku ja rakendatakse tegelikule DOM-ile ühe tõhusa operatsiooniga.
See protsess, mida tuntakse kui 'reconciliation', teebki Reacti nii jõudluspäraseks. Selle asemel, et kogu maja uuesti ehitada, tegutseb React nagu asjatundlik töövõtja, kes tuvastab täpselt, millised konkreetsed tellised vajavad väljavahetamist, minimeerides tööd ja häireid.
2. peatükk: Võtmeteta nimekirjade renderdamise probleem
Nüüd vaatame, kus see elegantne süsteem võib hätta sattuda. Vaatleme lihtsat komponenti, mis renderdab kasutajate nimekirja:
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<li>{user.name}</li>
))}
</ul>
);
}
Kui see komponent esimest korda renderdatakse, ehitab React VDOM-puu. Kui lisame uue kasutaja `users` massiivi *lõppu*, käsitleb Reacti 'diffing' algoritm seda sujuvalt. See võrdleb vana ja uut nimekirja, näeb lõpus uut elementi ja lihtsalt lisab uue `<li>` tegelikku DOM-i. Tõhus ja lihtne.
Aga mis juhtub, kui lisame uue kasutaja nimekirja *algusesse* või muudame elementide järjekorda?
Oletame, et meie algne nimekiri on:
- Alice
- Bob
Ja pärast uuendust saab sellest:
- Charlie
- Alice
- Bob
Ilma unikaalsete identifikaatoriteta võrdleb React kahte nimekirja nende järjekorra (indeksi) alusel. See näeb järgmist:
- Positsioon 0: Vana element oli "Alice". Uus element on "Charlie". React järeldab, et sellel positsioonil olevat komponenti tuleb uuendada. See muudab olemasolevat DOM-sõlme, et muuta selle sisu "Alice"-st "Charlie"-ks.
- Positsioon 1: Vana element oli "Bob". Uus element on "Alice". React muudab teist DOM-sõlme, et muuta selle sisu "Bob"-ist "Alice"-ks.
- Positsioon 2: Siin varem elementi polnud. Uus element on "Bob". React loob ja lisab uue DOM-sõlme "Bob" jaoks.
See on uskumatult ebaefektiivne. Selle asemel, et lihtsalt lisada üks uus element "Charlie" jaoks algusesse, tegi React kaks muutmist ja ühe lisamise. Suure nimekirja või keeruliste, oma olekuga nimekirjaelementide puhul põhjustab see tarbetu töö märkimisväärset jõudluse langust ja, mis veelgi olulisem, potentsiaalseid vigu komponendi olekuga.
Sellepärast, kui käivitate ülaltoodud koodi, kuvab teie brauseri arendajakonsool hoiatuse: "Warning: Each child in a list should have a unique 'key' prop." React ütleb teile otse, et vajab oma töö tõhusaks tegemiseks abi.
3. peatükk: `key` atribuut appi
Atribuut `key` on vihje, mida React vajab. See on spetsiaalne string-atribuut, mille annate elementide nimekirjade loomisel. Võtmed annavad igale elemendile stabiilse ja unikaalse identiteedi renderduste vahel.
Kirjutame oma `UserList` komponendi ümber võtmetega:
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Siin eeldame, et igal `user` objektil on unikaalne `id` omadus (näiteks andmebaasist). Nüüd vaatame uuesti meie stsenaariumi.
Algandmed:
[{ id: 'u1', name: 'Alice' }, { id: 'u2', name: 'Bob' }]
Uuendatud andmed:
[{ id: 'u3', name: 'Charlie' }, { id: 'u1', name: 'Alice' }, { id: 'u2', name: 'Bob' }]
Võtmetega on Reacti 'diffing' protsess palju nutikam:
- React vaatab uue VDOM-i `<ul>` lastelemente ja kontrollib nende võtmeid. See näeb `u3`, `u1` ja `u2`.
- Seejärel kontrollib see eelmise VDOM-i lastelemente ja nende võtmeid. See näeb `u1` ja `u2`.
- React teab, et komponendid võtmetega `u1` ja `u2` on juba olemas. See ei pea neid muutma; see peab lihtsalt nende vastavad DOM-sõlmed uutele positsioonidele liigutama.
- React näeb, et võti `u3` on uus. See loob uue komponendi ja DOM-sõlme "Charlie" jaoks ning lisab selle algusesse.
Tulemuseks on üks DOM-i lisamine ja mõningane ümberjärjestamine, mis on palju tõhusam kui mitu muutmist ja lisamine, mida nägime varem. Võtmed pakuvad stabiilset identiteeti, võimaldades Reactil elemente renderduste vahel jälgida, sõltumata nende asukohast massiivis.
4. peatükk: Õige võtme valimine – kuldreeglid
Atribuudi `key` tõhusus sõltub täielikult õige väärtuse valimisest. On selged parimad praktikad ja ohtlikud antimustrid, mida tuleb teada.
Parim võti: unikaalsed ja stabiilsed ID-d
Ideaalne võti on väärtus, mis tuvastab elemendi nimekirjas unikaalselt ja püsivalt. See on peaaegu alati unikaalne ID teie andmeallikast.
- See peab olema oma sõsarate seas unikaalne. Võtmed ei pea olema globaalselt unikaalsed, vaid ainult sellel tasemel renderdatavate elementide nimekirjas. Kahel erineval nimekirjal samal lehel võivad olla sama võtmega elemendid.
- See peab olema stabiilne. Konkreetse andmeelemendi võti ei tohiks renderduste vahel muutuda. Kui laete uuesti Alice'i andmeid, peaks tal endiselt olema sama `id`.
Suurepärased allikad võtmete jaoks on:
- Andmebaasi primaarvõtmed (nt `user.id`, `product.sku`)
- Universaalselt unikaalsed identifikaatorid (UUID-d)
- Unikaalne, muutumatu string teie andmetest (nt raamatu ISBN)
// HEA: Kasutades andmetest pärinevat stabiilset ja unikaalset ID-d.
<div>
{products.map(product => (
<ProductItem key={product.sku} product={product} />
))}
</div>
Antimuster: massiivi indeksi kasutamine võtmena
Levinud viga on kasutada massiivi indeksit võtmena:
// HALB: Kasutades massiivi indeksit võtmena.
<div>
{items.map((item, index) => (
<ListItem key={index} item={item} />
))}
</div>
Kuigi see vaigistab Reacti hoiatuse, võib see põhjustada tõsiseid probleeme ja seda peetakse üldiselt antimustriks. Indeksi kasutamine võtmena ütleb Reactile, et elemendi identiteet on seotud selle asukohaga nimekirjas. See on põhimõtteliselt sama probleem kui võtme puudumine, kui nimekirja saab ümber järjestada, filtreerida või kui elemente lisatakse/eemaldatakse algusest või keskelt.
Olekuhalduse viga:
Kõige ohtlikum kõrvalmõju indeksi võtmete kasutamisel ilmneb siis, kui teie nimekirja elemendid haldavad oma olekut. Kujutage ette sisestusväljade nimekirja:
function UnstableList() {
const [items, setItems] = React.useState([{ id: 1, text: 'First' }, { id: 2, text: 'Second' }]);
const handleAddItemToTop = () => {
setItems([{ id: 3, text: 'New Top' }, ...items]);
};
return (
<div>
<button onClick={handleAddItemToTop}>Add to Top</button>
{items.map((item, index) => (
<div key={index}>
<label>{item.text}: </label>
<input type="text" />
</div>
))}
</div>
);
}
Proovige seda mõtteharjutust:
- Nimekiri renderdatakse elementidega "First" ja "Second".
- Kirjutate esimesse sisestusvälja (see, mis on "First" jaoks) "Hello".
- Klõpsate nupul "Add to Top".
Mida te ootate, et juhtub? Ootaksite, et ilmub uus, tühi sisestusväli "New Top" jaoks ja sisestusväli "First" jaoks (mis sisaldab endiselt "Hello") liigub allapoole. Mis tegelikult juhtub? Esimesel positsioonil (indeks 0) asuv sisestusväli, mis sisaldab endiselt "Hello", jääb alles. Aga nüüd on see seotud uue andmeelemendiga, "New Top". Sisestuskomponendi olek (selle sisemine väärtus) on seotud selle positsiooniga (key=0), mitte andmetega, mida see peaks esindama. See on klassikaline ja segadusttekitav viga, mille põhjustavad indeksi võtmed.
Kui te lihtsalt muudate `key={index}` väärtuseks `key={item.id}`, on probleem lahendatud. React seostab nüüd komponendi oleku korrektselt andmete stabiilse ID-ga.
Millal on indeksi kasutamine võtmena aktsepteeritav?
On haruldasi olukordi, kus indeksi kasutamine on ohutu, kuid peate täitma kõik need tingimused:
- Nimekiri on staatiline: seda ei järjestata kunagi ümber, ei filtreerita ega lisata/eemaldata elemente mujalt kui lõpust.
- Nimekirja elementidel pole stabiilseid ID-sid.
- Iga elemendi jaoks renderdatud komponendid on lihtsad ja neil pole sisemist olekut.
Isegi siis on sageli parem genereerida ajutine, kuid stabiilne ID, kui see on võimalik. Indeksi kasutamine peaks alati olema teadlik valik, mitte vaikimisi lahendus.
Kõige hullem variant: `Math.random()`
Ärge kunagi kasutage `Math.random()` või mõnda muud mittedeterministlikku väärtust võtmena:
// KOHUTAV: Ärge tehke seda!
<div>
{items.map(item => (
<ListItem key={Math.random()} item={item} />
))}
</div>
`Math.random()`-ga genereeritud võti on garanteeritult erinev igal renderdusel. See ütleb Reactile, et kogu eelmisest renderdusest pärit komponentide nimekiri on hävitatud ja on loodud täiesti uus nimekiri täiesti erinevatest komponentidest. See sunnib Reacti kõik vanad komponendid lahti ühendama (hävvitades nende oleku) ja kõik uued komponendid ühendama. See nullib täielikult 'reconciliation' eesmärgi ja on jõudluse seisukohast halvim võimalik valik.
5. peatükk: Täpsemad kontseptsioonid ja korduma kippuvad küsimused
Võtmed ja `React.Fragment`
Mõnikord peate tagastama `map` tagasikutse funktsioonist mitu elementi. Standardne viis selleks on `React.Fragment`. Kui te seda teete, tuleb `key` paigutada `Fragment` komponendile endale.
function Glossary({ terms }) {
return (
<dl>
{terms.map(term => (
// Võti läheb Fragmentile, mitte lastelementidele.
<React.Fragment key={term.id}>
<dt>{term.name}</dt>
<dd>{term.definition}</dd>
</React.Fragment>
))}
</dl>
);
}
Oluline: Lühendatud süntaks `<>...</>` ei toeta võtmeid. Kui teie nimekiri nõuab fragmente, peate kasutama selgesõnalist `<React.Fragment>` süntaksit.
Võtmed peavad olema unikaalsed ainult sõsarate seas
Levinud eksiarvamus on, et võtmed peavad olema globaalselt unikaalsed kogu teie rakenduses. See ei ole tõsi. Võti peab olema unikaalne ainult oma vahetute sõsarate nimekirjas.
function CourseRoster({ courses }) {
return (
<div>
{courses.map(course => (
<div key={course.id}> {/* Võti kursuse jaoks */}
<h3>{course.title}</h3>
<ul>
{course.students.map(student => (
// See õpilase võti peab olema unikaalne ainult selle konkreetse kursuse õpilaste nimekirjas.
<li key={student.id}>{student.name}</li>
))}
</ul>
</div>
))}
</div>
);
}
Ülaltoodud näites võib kahel erineval kursusel olla õpilane ID-ga `'s1'`. See on täiesti korras, sest võtmeid hinnatakse erinevate vanemelementide `<ul>` sees.
Võtmete kasutamine komponendi oleku teadlikuks lähtestamiseks
Kuigi võtmed on peamiselt nimekirjade optimeerimiseks, on neil sügavam eesmärk: nad defineerivad komponendi identiteedi. Kui komponendi võti muutub, ei ürita React olemasolevat komponenti uuendada. Selle asemel hävitab see vana komponendi (ja kõik selle lapsed) ja loob nullist täiesti uue. See ühendab lahti vana instantsi ja ühendab uue, lähtestades sellega tõhusalt selle oleku.
See võib olla võimas ja deklaratiivne viis komponendi lähtestamiseks. Kujutage näiteks ette `UserProfile` komponenti, mis hangib andmeid `userId` alusel.
function App() {
const [userId, setUserId] = React.useState('user-1');
return (
<div>
<button onClick={() => setUserId('user-1')}>View User 1</button>
<button onClick={() => setUserId('user-2')}>View User 2</button>
<UserProfile key={userId} id={userId} />
</div>
);
}
Paigutades `key={userId}` `UserProfile` komponendile, garanteerime, et iga kord, kui `userId` muutub, visatakse kogu `UserProfile` komponent minema ja luuakse uus. See hoiab ära potentsiaalsed vead, kus eelmise kasutaja profiili olek (näiteks vormiandmed või hangitud sisu) võib alles jääda. See on puhas ja selgesõnaline viis komponendi identiteedi ja elutsükli haldamiseks.
Kokkuvõte: parema Reacti koodi kirjutamine
Atribuut `key` on palju enamat kui lihtsalt viis konsoolihoiatuse vaigistamiseks. See on fundamentaalne juhis Reactile, pakkudes kriitilist teavet, mis on vajalik selle 'reconciliation' algoritmi tõhusaks ja korrektseks toimimiseks. Võtmete kasutamise valdamine on professionaalse Reacti arendaja tunnusmärk.
Võtame kokku peamised punktid:
- Võtmed on jõudluse jaoks hädavajalikud: Need võimaldavad Reacti 'diffing' algoritmil tõhusalt lisada, eemaldada ja ümber järjestada elemente nimekirjas ilma tarbetute DOM-i muudatusteta.
- Kasutage alati stabiilseid ja unikaalseid ID-sid: Parim võti on unikaalne identifikaator teie andmetest, mis ei muutu renderduste vahel.
- Vältige massiivi indekseid võtmetena: Elemendi indeksi kasutamine võtmena võib põhjustada kehva jõudlust ning peeneid ja frustreerivaid olekuhalduse vigu, eriti dünaamilistes nimekirjades.
- Ärge kunagi kasutage juhuslikke või ebastabiilseid võtmeid: See on halvim stsenaarium, kuna see sunnib Reacti looma kogu komponentide nimekirja uuesti igal renderdusel, hävitades jõudluse ja oleku.
- Võtmed defineerivad komponendi identiteedi: Saate seda käitumist ära kasutada komponendi oleku teadlikuks lähtestamiseks, muutes selle võtit.
Nende põhimõtete omaksvõtmisega ei kirjuta te mitte ainult kiiremaid ja usaldusväärsemaid Reacti rakendusi, vaid saate ka sügavama arusaama teegi põhituumast. Järgmine kord, kui teete massiivist `map` funktsiooniga nimekirja renderdamiseks, pöörake `key` atribuudile väärilist tähelepanu. Teie rakenduse jõudlus – ja teie tulevane mina – tänavad teid selle eest.