Lietuvių

Įvaldykite „React“ suderinimo procesą. Sužinokite, kaip teisingas 'key' atributo naudojimas optimizuoja sąrašų atvaizdavimą, apsaugo nuo klaidų ir pagerina programos našumą. Gidas pasaulio programuotojams.

Našumo atrakinimas: išsami „React“ suderinimo raktų analizė sąrašų optimizavimui

Šiuolaikiniame žiniatinklio kūrimo pasaulyje nepaprastai svarbu kurti dinamiškas vartotojo sąsajas, kurios greitai reaguotų į duomenų pokyčius. „React“, su savo komponentais pagrįsta architektūra ir deklaratyviu pobūdžiu, tapo pasauliniu standartu kuriant šias sąsajas. „React“ efektyvumo pagrindas yra procesas, vadinamas suderinimu (angl. reconciliation), kuriame dalyvauja virtualus DOM. Tačiau net ir patys galingiausi įrankiai gali būti naudojami neefektyviai, o viena iš sričių, kurioje dažnai klysta tiek nauji, tiek patyrę programuotojai, yra sąrašų atvaizdavimas.

Tikriausiai ne kartą esate rašę kodą, panašų į data.map(item => <div>{item.name}</div>). Atrodo paprasta, beveik trivialu. Tačiau po šiuo paprastumu slypi kritiškai svarbus našumo aspektas, kurio ignoravimas gali lemti lėtai veikiančias programas ir sunkiai paaiškinamas klaidas. Sprendimas? Mažas, bet galingas atributas: key.

Šis išsamus vadovas leis jums pasigilinti į „React“ suderinimo procesą ir nepakeičiamą raktų vaidmenį atvaizduojant sąrašus. Išnagrinėsime ne tik „ką“, bet ir „kodėl“ – kodėl raktai yra būtini, kaip juos teisingai pasirinkti ir kokios yra reikšmingos neteisingo pasirinkimo pasekmės. Baigę skaityti, turėsite žinių, kaip rašyti našesnes, stabilesnes ir profesionalesnes „React“ programas.

1 skyrius: „React“ suderinimo ir virtualaus DOM supratimas

Prieš pradedant vertinti raktų svarbą, pirmiausia turime suprasti pagrindinį mechanizmą, dėl kurio „React“ yra greitas: suderinimą, kurį įgalina virtualus DOM (VDOM).

Kas yra virtualus DOM?

Tiesioginė sąveika su naršyklės dokumento objektų modeliu (DOM) yra skaičiavimų reikalaujantis procesas. Kiekvieną kartą, kai keičiate ką nors DOM – pavyzdžiui, pridedate mazgą, atnaujinate tekstą ar keičiate stilių – naršyklė turi atlikti daug darbo. Jai gali tekti perskaičiuoti viso puslapio stilius ir išdėstymą – šis procesas žinomas kaip „reflow“ ir „repaint“. Sudėtingoje, duomenimis pagrįstoje programoje, dažnos tiesioginės DOM manipuliacijos gali greitai smarkiai sumažinti našumą.

„React“ šią problemą sprendžia įvesdama abstrakcijos sluoksnį: virtualų DOM. VDOM yra lengvas, atmintyje saugomas realaus DOM atvaizdas. Galvokite apie jį kaip apie savo vartotojo sąsajos brėžinį. Kai liepiate „React“ atnaujinti vartotojo sąsają (pavyzdžiui, pakeitus komponento būseną), „React“ iš karto neliečia realaus DOM. Vietoj to, jis atlieka šiuos veiksmus:

  1. Sukuriama nauja VDOM medžio struktūra, atspindinti atnaujintą būseną.
  2. Ši nauja VDOM medžio struktūra palyginama su ankstesne. Šis palyginimo procesas vadinamas „skirtumų nustatymu“ (angl. diffing).
  3. „React“ išsiaiškina minimalų pakeitimų rinkinį, reikalingą senąjį VDOM paversti naujuoju.
  4. Šie minimalūs pakeitimai sugrupuojami ir pritaikomi realiam DOM vienos efektyvios operacijos metu.

Šis procesas, žinomas kaip suderinimas, ir yra tai, kas daro „React“ tokiu našiu. Užuot perstatęs visą namą, „React“ veikia kaip ekspertas rangovas, kuris tiksliai nustato, kurias konkrečias plytas reikia pakeisti, taip sumažindamas darbo apimtį ir trikdžius.

2 skyrius: Problema atvaizduojant sąrašus be raktų

Dabar pažiūrėkime, kur ši elegantiška sistema gali susidurti su problemomis. Apsvarstykite paprastą komponentą, kuris atvaizduoja vartotojų sąrašą:


function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li>{user.name}</li>
      ))}
    </ul>
  );
}

Kai šis komponentas atvaizduojamas pirmą kartą, „React“ sukuria VDOM medžio struktūrą. Jei į `users` masyvo *pabaigą* pridedame naują vartotoją, „React“ skirtumų nustatymo algoritmas tai puikiai apdoroja. Jis palygina seną ir naują sąrašus, mato naują elementą pabaigoje ir tiesiog prideda naują `<li>` prie realaus DOM. Efektyvu ir paprasta.

Bet kas nutinka, jei pridedame naują vartotoją į sąrašo pradžią arba pertvarkome elementus?

Tarkime, mūsų pradinis sąrašas yra toks:

Ir po atnaujinimo jis tampa toks:

Be jokių unikalių identifikatorių, „React“ lygina du sąrašus pagal jų tvarką (indeksą). Štai ką jis mato:

Tai neįtikėtinai neefektyvu. Vietoj to, kad tiesiog įterptų vieną naują elementą „Charlie“ pradžioje, „React“ atliko dvi modifikacijas ir vieną įterpimą. Dideliame sąraše arba kai sąrašo elementai yra sudėtingi komponentai su savo būsena, šis nereikalingas darbas lemia didelį našumo sumažėjimą ir, kas dar svarbiau, galimas klaidas, susijusias su komponento būsena.

Štai kodėl, jei paleisite aukščiau pateiktą kodą, jūsų naršyklės programuotojo konsolėje pamatysite įspėjimą: „Warning: Each child in a list should have a unique 'key' prop.“ „React“ aiškiai jums sako, kad jam reikia pagalbos, kad galėtų efektyviai atlikti savo darbą.

3 skyrius: `key` atributas ateina į pagalbą

key atributas yra užuomina, kurios reikia „React“. Tai specialus eilutės tipo atributas, kurį nurodote kurdami elementų sąrašus. Raktai suteikia kiekvienam elementui stabilų ir unikalų identitetą tarp pakartotinių atvaizdavimų.

Perrašykime mūsų `UserList` komponentą su raktais:


function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Čia darome prielaidą, kad kiekvienas `user` objektas turi unikalią `id` savybę (pvz., iš duomenų bazės). Dabar grįžkime prie mūsų scenarijaus.

Pradiniai duomenys:


[{ id: 'u1', name: 'Alice' }, { id: 'u2', name: 'Bob' }]

Atnaujinti duomenys:


[{ id: 'u3', name: 'Charlie' }, { id: 'u1', name: 'Alice' }, { id: 'u2', name: 'Bob' }]

Su raktais „React“ skirtumų nustatymo procesas yra daug protingesnis:

  1. „React“ peržiūri naujojo VDOM `<ul>` vaikus ir patikrina jų raktus. Jis mato `u3`, `u1` ir `u2`.
  2. Tada jis patikrina ankstesnio VDOM vaikus ir jų raktus. Jis mato `u1` ir `u2`.
  3. „React“ žino, kad komponentai su raktais `u1` ir `u2` jau egzistuoja. Jam nereikia jų modifikuoti; jam tereikia perkelti jų atitinkamus DOM mazgus į naujas pozicijas.
  4. „React“ mato, kad raktas `u3` yra naujas. Jis sukuria naują komponentą ir DOM mazgą „Charlie“ ir įterpia jį pradžioje.

Rezultatas yra vienas DOM įterpimas ir šiek tiek pertvarkymo, kas yra daug efektyviau nei kelios modifikacijos ir įterpimas, kuriuos matėme anksčiau. Raktai suteikia stabilų identitetą, leidžiantį „React“ sekti elementus tarp atvaizdavimų, nepaisant jų pozicijos masyve.

4 skyrius: Teisingo rakto pasirinkimas – auksinės taisyklės

`key` atributo efektyvumas visiškai priklauso nuo teisingos reikšmės pasirinkimo. Yra aiškios gerosios praktikos ir pavojingi antimodeliai, kuriuos reikia žinoti.

Geriausias raktas: unikalūs ir stabilūs ID

Idealus raktas yra reikšmė, kuri unikaliai ir visam laikui identifikuoja elementą sąraše. Tai beveik visada yra unikalus ID iš jūsų duomenų šaltinio.

Puikūs raktų šaltiniai yra:


// GERAI: Naudojamas stabilus, unikalus ID iš duomenų.
<div>
  {products.map(product => (
    <ProductItem key={product.sku} product={product} />
  ))}
</div>

Antimodelis: masyvo indekso naudojimas kaip rakto

Dažna klaida yra naudoti masyvo indeksą kaip raktą:


// BLOGAI: Naudojamas masyvo indeksas kaip raktas.
<div>
  {items.map((item, index) => (
    <ListItem key={index} item={item} />
  ))}
</div>

Nors tai nutildys „React“ įspėjimą, tai gali sukelti rimtų problemų ir paprastai laikoma antimodeliais (bloga praktika). Indekso naudojimas kaip rakto nurodo „React“, kad elemento identitetas yra susietas su jo pozicija sąraše. Tai iš esmės ta pati problema, kaip ir neturint rakto, kai sąrašas gali būti pertvarkomas, filtruojamas arba kai elementai pridedami/šalinami iš pradžios ar vidurio.

Būsenos valdymo klaida:

Pavojingiausias šalutinis indekso raktų naudojimo poveikis pasireiškia, kai jūsų sąrašo elementai valdo savo būseną. Įsivaizduokite įvesties laukų sąrašą:


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}>Pridėti į viršų</button>
      {items.map((item, index) => (
        <div key={index}>
          <label>{item.text}: </label>
          <input type="text" />
        </div>
      ))}
    </div>
  );
}

Išbandykite šį mintinį pratimą:

  1. Sąrašas atvaizduojamas su „First“ ir „Second“.
  2. Į pirmąjį įvesties lauką (skirtą „First“) įvedate „Hello“.
  3. Spustelite mygtuką „Pridėti į viršų“.

Ko tikitės, kad nutiks? Tikėtumėtės, kad atsiras naujas, tuščias įvesties laukas „New Top“, o įvesties laukas „First“ (vis dar turintis „Hello“) pasislinks žemyn. Kas iš tikrųjų nutinka? Įvesties laukas pirmoje pozicijoje (indeksas 0), kuriame vis dar yra „Hello“, lieka. Bet dabar jis yra susietas su nauju duomenų elementu, „New Top“. Įvesties komponento būsena (jo vidinė reikšmė) yra susieta su jo pozicija (raktas=0), o ne su duomenimis, kuriuos jis turėtų atspindėti. Tai klasikinė ir paini klaida, sukelta indekso raktų.

Jei tiesiog pakeisite `key={index}` į `key={item.id}`, problema bus išspręsta. „React“ dabar teisingai susies komponento būseną su stabiliu duomenų ID.

Kada priimtina naudoti indekso raktą?

Yra retų situacijų, kai indekso naudojimas yra saugus, tačiau turite atitikti visas šias sąlygas:

  1. Sąrašas yra statiškas: jis niekada nebus pertvarkomas, filtruojamas ar neturės elementų, pridedamų/šalinamų iš bet kurios kitos vietos, išskyrus pabaigą.
  2. Sąrašo elementai neturi stabilių ID.
  3. Kiekvienam elementui atvaizduojami komponentai yra paprasti ir neturi vidinės būsenos.

Net ir tada dažnai geriau sugeneruoti laikiną, bet stabilų ID, jei įmanoma. Indekso naudojimas visada turėtų būti sąmoningas pasirinkimas, o ne numatytasis.

Didžiausias piktadarys: `Math.random()`

Niekada, niekada nenaudokite `Math.random()` ar bet kurios kitos nedeterministinės reikšmės raktui:


// TRAGIŠKA: Nedarykite to!
<div>
  {items.map(item => (
    <ListItem key={Math.random()} item={item} />
  ))}
</div>

Raktas, sugeneruotas naudojant `Math.random()`, garantuotai bus skirtingas kiekvieno atvaizdavimo metu. Tai nurodo „React“, kad visas komponentų sąrašas iš ankstesnio atvaizdavimo buvo sunaikintas ir sukurtas visiškai naujas, visiškai skirtingų komponentų sąrašas. Tai priverčia „React“ atjungti visus senus komponentus (sunaikindamas jų būseną) ir prijungti visus naujus. Tai visiškai paneigia suderinimo tikslą ir yra blogiausias įmanomas pasirinkimas našumo požiūriu.

5 skyrius: Pažangesnės koncepcijos ir dažniausiai užduodami klausimai

Raktai ir `React.Fragment`

Kartais iš `map` atgalinio iškvietimo funkcijos (angl. callback) reikia grąžinti kelis elementus. Standartinis būdas tai padaryti yra naudojant `React.Fragment`. Kai tai darote, `key` turi būti priskirtas pačiam `Fragment` komponentui.


function Glossary({ terms }) {
  return (
    <dl>
      {terms.map(term => (
        // Raktas dedamas ant Fragment, o ne ant jo vaikų.
        <React.Fragment key={term.id}>
          <dt>{term.name}</dt>
          <dd>{term.definition}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}

Svarbu: Sutrumpinta sintaksė `<>...</>` nepalaiko raktų. Jei jūsų sąrašui reikalingi fragmentai, turite naudoti aiškią `<React.Fragment>` sintaksę.

Raktai turi būti unikalūs tik tarp gretimų elementų

Dažnai klaidingai manoma, kad raktai turi būti globaliai unikalūs visoje jūsų programoje. Tai netiesa. Raktas turi būti unikalus tik savo tiesioginių gretimų elementų sąraše.


function CourseRoster({ courses }) {
  return (
    <div>
      {courses.map(course => (
        <div key={course.id}>  {/* Raktas kursui */} 
          <h3>{course.title}</h3>
          <ul>
            {course.students.map(student => (
              // Šis studento raktas turi būti unikalus tik šio konkretaus kurso studentų sąraše.
              <li key={student.id}>{student.name}</li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
}

Aukščiau pateiktame pavyzdyje du skirtingi kursai galėtų turėti studentą su `id: 's1'`. Tai yra visiškai gerai, nes raktai yra vertinami skirtingų tėvinių `<ul>` elementų viduje.

Raktų naudojimas sąmoningam komponento būsenos atstatymui

Nors raktai pirmiausia skirti sąrašų optimizavimui, jie atlieka gilesnę funkciją: jie apibrėžia komponento identitetą. Jei komponento raktas pasikeičia, „React“ nebandys atnaujinti esamo komponento. Vietoj to, jis sunaikins seną komponentą (ir visus jo vaikus) ir sukurs visiškai naują nuo nulio. Tai atjungia seną egzempliorių ir prijungia naują, efektyviai atstatydamas jo būseną.

Tai gali būti galingas ir deklaratyvus būdas atstatyti komponentą. Pavyzdžiui, įsivaizduokite `UserProfile` komponentą, kuris gauna duomenis pagal `userId`.


function App() {
  const [userId, setUserId] = React.useState('user-1');

  return (
    <div>
      <button onClick={() => setUserId('user-1')}>Peržiūrėti vartotoją 1</button>
      <button onClick={() => setUserId('user-2')}>Peržiūrėti vartotoją 2</button>
      
      <UserProfile key={userId} id={userId} />
    </div>
  );
}

Priskirdami `key={userId}` `UserProfile` komponentui, mes garantuojame, kad kiekvieną kartą pasikeitus `userId`, visas `UserProfile` komponentas bus išmestas ir sukurtas naujas. Tai apsaugo nuo galimų klaidų, kai ankstesnio vartotojo profilio būsena (pvz., formos duomenys ar gautas turinys) galėtų išlikti. Tai švarus ir aiškus būdas valdyti komponento identitetą ir gyvavimo ciklą.

Išvada: geresnio „React“ kodo rašymas

`key` atributas yra daug daugiau nei būdas nutildyti konsolės įspėjimą. Tai yra pagrindinė instrukcija „React“, teikianti kritinę informaciją, reikalingą jo suderinimo algoritmui efektyviai ir teisingai veikti. Raktų naudojimo įvaldymas yra profesionalaus „React“ programuotojo požymis.

Apibendrinkime pagrindinius teiginius:

Įsisavinę šiuos principus, jūs ne tik rašysite greitesnes, patikimesnes „React“ programas, bet ir giliau suprasite bibliotekos pagrindinius mechanizmus. Kitą kartą, kai iteruosite per masyvą, kad atvaizduotumėte sąrašą, skirkite `key` atributui deramą dėmesį. Jūsų programos našumas – ir jūsų ateities „aš“ – jums už tai padėkos.