Oleku käitlemine reaktsioonis: kaaluda tuleb nelja muutumatut lähenemisviisi

Võib-olla on tänapäeval Reactis kõige tavalisem segadusepunkt: osariik.

Kujutage ette, et teil on vorm kasutaja muutmiseks. Kõigi vormiväljade muudatuste käsitlemiseks on tavaline luua üks muudatuste käitleja. See võib välja näha umbes selline:

updateState(event) { const {name, value} = event.target; let user = this.state.user; // this is a reference, not a copy... user[name] = value; // so this mutates state ? return this.setState({user}); }

Mure on real 4. Rida 4 muteerib tegelikult olekut, kuna kasutajamuutuja on viide olekule. Reaktsioonirežiimi tuleks käsitleda muutumatuna.

Reagendi dokumentidest:

Ärge kunagi muteeruge this.stateotse, sest setState()pärast helistamine võib teie tehtud mutatsiooni asendada. Kohtle this.statenii, nagu oleks see muutumatu.

Miks?

  1. setState'i partiid töötavad kulisside taga. See tähendab, et setState'i töötlemisel võidakse manuaalne olekmutatsioon tühistada.
  2. Kui kuulutate meetodi peaks olemaComponentUpdate, ei saa te sees kasutada === võrdsuse kontrolli, kuna objekti viide ei muutu . Nii et ülaltoodud lähenemisviisil on ka potentsiaalne tulemuslikkuse mõju.

Alumine rida : ülaltoodud näide töötab sageli hästi, kuid servajuhtumite vältimiseks kohtle olekut muutumatuna.

Siin on neli viisi, kuidas seisundit muuta muutumatuna:

Lähenemine nr 1: Objekt.omistamine

Objekt.objekt loob objekti koopia. Esimene parameeter on sihtmärk, seejärel määrate ühe või mitu parameetrit omadustele, millele soovite kleepida. Nii et ülaltoodud näite parandamine hõlmab 3. rea lihtsat muutmist:

updateState(event) { const {name, value} = event.target; let user = Object.assign({}, this.state.user); user[name] = value; return this.setState({user}); }

3. real ütlen ma: "Looge uus tühi objekt ja lisage sellele kõik saidil this.state.user olevad atribuudid." See loob olekusse salvestatud kasutajaobjektist eraldi koopia. Nüüd on mul turvaline 4. real oleva kasutajaobjekti muteerimine - see on olekus olevast objektist täiesti eraldi objekt.

Täitke objekt Object.ohib kindlasti, kuna see pole IE-s toetatud ja Babel ei transleeri seda. Neli võimalust kaaluda:

  1. objekt-määrama
  2. MDN-i dokumendid
  3. Paabeli polüfill
  4. Polyfill.io

Lähenemine nr 2: objekti levik

Objektide levik on praegu 3. etapi funktsioon ja Babel saab selle üle kanda. See lähenemisviis on ülevaatlikum:

updateState(event) { const {name, value} = event.target; let user = {...this.state.user, [name]: value}; this.setState({user}); }

Reas 3 ütlen ma: "Uue objekti loomiseks kasutage kõiki atribuute saidil this.state.user, seejärel määrake atribuut, mida tähistab [nimi], uuele väärtusele, mis edastatakse sündmusele.target.value". Nii et see lähenemine toimib sarnaselt lähenemisviisile Objekt. Määramine, kuid sellel on kaks eelist:

  1. Polüfiltreerimist pole vaja, kuna Paabel suudab üle kanda
  2. Kokkuvõtlikum

Selle ühe voodri loomiseks võite kasutada isegi ümberstruktureerimist ja sissetoomist:

updateState({target}) { this.setState({user: {...this.state.user, [target.name]: target.value}}); }

Hävitan meetodi allkirjas sündmuse, et saada viide sündmusele.target. Siis ma deklareerin, et olekuks tuleks määrata selle.state.user koopia, kusjuures vastava atribuudiga määratakse uus väärtus. Mulle meeldib, kui kitsas see on. Praegu on see minu lemmikkäsitlus muutuste haldurite kirjutamisel. ?

Need kaks ülaltoodud lähenemisviisi on kõige tavalisemad ja otsekohesemad viisid muutumatu seisundi käsitlemiseks. Kas soovite rohkem energiat? Vaadake kahte muud valikut allpool.

Lähenemine nr 3: muutmatuse abistaja

Muutumatuse abistaja on mugav teek andmete koopia muutmiseks allikat muutmata. Seda teeki soovitatakse Reacti dokumentides.

// Import at the top: import update from 'immutability-helper'; updateState({target}) { let user = update(this.state.user, {$merge: {[target.name]: target.value}}); this.setState({user}); }

5. real kutsun ma ühendamist, mis on üks paljudest muutumatuse abistaja käskudest. Sarnaselt objektile Object.assign edastan selle esimese parameetrina sihtobjekti ja määran seejärel atribuudi, kuhu tahaksin liituda.

Muutmatuse abistajal on palju muud. See kasutab MongoDB päringukeelest inspireeritud süntaksit ja pakub mitmesuguseid võimsaid viise muutumatute andmetega töötamiseks.

Lähenemine nr 4: Immutable.js

Kas soovite süstemaatiliselt jõustada muutumatust? Mõelge immutable.js-le. See teek pakub muutumatuid andmestruktuure.

Siin on näide muutumatu kaardi kasutamisest:

 // At top, import immutable import { Map } from 'immutable'; // Later, in constructor... this.state = { // Create an immutable map in state using immutable.js user: Map({ firstName: 'Cory', lastName: 'House'}) }; updateState({target}) { // this line returns a new user object assuming an immutable map is stored in state. let user = this.state.user.set(target.name, target.value); this.setState({user}); }

Eespool on kolm peamist sammu:

  1. Import muutumatu.
  2. Määra olek konstruktori muutumatule kaardile
  3. Kasutage kasutaja uue koopia loomiseks muudatuste käitlemise seadistatud meetodit.

Immutable.js ilu: kui proovite seisundit otse muteerida, siis see ebaõnnestub . Teiste ülaltoodud lähenemisviiside puhul on seda lihtne unustada ja React ei hoiatagi teid, kui te seisundit otseselt muteerite.

Muutumatute varjuküljed?

  1. Paisuta . Immutable.js lisab teie kogusse 57K minimeeritud. Arvestades, et sellised teegid nagu Preact, võivad Reacti asendada ainult 3K-s, on seda raske aktsepteerida.
  2. Süntaks . Objekti omadustele peate viitama stringide ja meetodi kutsete kaudu, mitte otse. Ma eelistan kasutajanime user.get (nimi) asemel.
  3. YATTL (veel üks asi, mida õppida) - igaüks, kes teie meeskonnaga liitub, peab andmete hankimiseks ja seadistamiseks õppima veel ühe API, samuti uue andmetüüpide komplekti.

Veel paar huvitavat alternatiivi, mida kaaluda:

  • sujuv-muutumatu
  • reageeri-kopeeri-kirjuta

Hoiatus. Pange tähele pesastatud objekte!

Ülaltoodud võimalused nr 1 ja nr 2 (Object.assign ja Object spread) teevad ainult madalat klooni. Nii et kui teie objekt sisaldab pesastatud objekte, kopeeritakse need pesastatud objektid väärtuse asemel viitena . Nii et kui muudate pesastatud objekti, muteerite algse objekti. ?

Ole kloonimise osas kirurgiline. Ärge kloonige kõiki asju. Kloonige muutunud objektid. Muutmatus-abistaja (eespool mainitud) muudab selle lihtsaks. Nagu ka alternatiivid nagu immer, updeep või pikk nimekiri alternatiividest.

Teil võib tekkida kiusatus jõuda selliste sügavate liitmisvahenditeni nagu kloonid sügavale või lodash.merge, kuid vältige pimesi sügavat kloonimist .

Sellepärast:

  1. Sügav kloonimine on kallis.
  2. Sügav kloonimine on tavaliselt raiskav (kloonige selle asemel ainult see, mis on tegelikult muutunud)
  3. Sügav kloonimine põhjustab tarbetuid renderdusi, kuna Reacti arvates on kõik muutunud, ehkki tegelikult on muutunud võib-olla ainult konkreetne lapseobjekt.

Täname Dan Abramovit eespool mainitud ettepanekute eest:

Ma ei usu, et cloneDeep () on hea soovitus. See võib olla väga kallis. Kopeerige ainult need osad, mis tegelikult muutusid. Raamatukogud, nagu muutmatuse abistaja (//t.co/YadMmpiOO8), updeep (//t.co/P0MzD19hcD) või immer (//t.co/VyRa6Cd4IP), aitavad seda.

- Dan Abramov (@dan_abramov) 23. aprill 2018

Viimane näpunäide: kaaluge funktsionaalse setState kasutamist

Üks teine ​​korts võib sind hammustada:

setState () ei muuda seda kohe.seisundit, vaid loob ootel oleku ülemineku. Pärast selle meetodi kutsumist pääseb aadressile state.state tagasi.

Kuna setState'i kõned on pakendatud, viib selline kood vea:

updateState({target}) { this.setState({user: {...this.state.user, [target.name]: target.value}}); doSomething(this.state.user) // Uh oh, setState merely schedules a state change, so this.state.user may still have old value }

If you want to run code after a setState call has completed, use the callback form of setState:

updateState({target}) { this.setState((prevState) => { const updatedUser = {...prevState.user, [target.name]: target.value}; // use previous value in state to build new state... return { user: updatedUser }; // And what I return here will be set as the new state }, () => this.doSomething(this.state.user); // Now I can safely utilize the new state I've created to call other funcs... ); }

My Take

I admire the simplicity and light weight of option #2 above: Object spread. It doesn’t require a polyfill or separate library, I can declare a change handler on a single line, and I can be surgical about what has changed. ? Working with nested object structures? I currently prefer Immer.

Have other ways you like to handle state in React? Please chime in via the comments!

Looking for More on React? ⚛

I’ve authored multiple React and JavaScript courses on Pluralsight (free trial). My latest, “Creating Reusable React Components” just published! ?

Cory House on Pluralsighti kohta mitme JavaScripti, Reacti, puhta koodi, .NETi ja muu kursuse autor. Ta on Microsofti MVP-i VinSolutions tarkvaraarhitekt reakjsconsulting.com peamine konsultant ning koolitab tarkvaraarendajaid rahvusvaheliselt tarkvarapraktikate osas, nagu esiotsa arendamine ja puhas kodeerimine. Cory säutsub JavaScripti ja kasutajaliidese arendamise kohta Twitteris aadressil @housecor.