Reagointitila: neljä harkittavaa lähestymistapaa

Ehkä yleisin sekaannuskohta Reactissa tänään: tila.

Kuvittele, että sinulla on lomake käyttäjän muokkaamiseen. On tavallista luoda yksi muutoksen käsittelijä kaikkien lomakekenttien muutosten käsittelemiseksi. Se voi näyttää tältä:

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}); }

Huolenaihe on linjalla 4. Linja 4 itse asiassa mutatoi tilan, koska käyttäjämuuttuja viittaa tilaan. Reaktiotilaa tulisi pitää muuttumattomana.

React-asiakirjoista:

Älä koskaan mutatoi this.statesuoraan, sillä soittaminen setState()jälkikäteen voi korvata tekemäsi mutaation. Kohtele this.stateikään kuin se olisi muuttumaton.

Miksi?

  1. setState-erät toimivat kulissien takana. Tämä tarkoittaa, että manuaalinen tilamutaatio voidaan ohittaa, kun setState käsitellään.
  2. Jos ilmoitat shouldComponentUpdate-menetelmän, et voi käyttää === tasaustarkistusta sisällä, koska objektiviite ei muutu . Joten yllä oleva lähestymistapa voi vaikuttaa myös suorituskykyyn.

Bottom line : Yllä oleva esimerkki toimii usein kunnossa, mutta välttää reunatapauksia, pidä tilaa muuttumattomana.

Tässä on neljä tapaa käsitellä tilaa muuttumattomana:

Lähestymistapa # 1: Objektin määritys

Object.assign luo kopion objektista. Ensimmäinen parametri on kohde, minkä jälkeen määrität yhden tai useamman parametrin ominaisuuksille, joihin haluat tarttua. Joten yllä olevan esimerkin korjaaminen edellyttää yksinkertaista muutosta riville 3:

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

Rivillä 3 sanon "Luo uusi tyhjä objekti ja lisää siihen kaikki tämän.state.user-sivuston ominaisuudet." Tämä luo erillisen kopion tilaobjektista käyttäjäobjektista. Nyt voin turvallisesti muokata käyttäjän objektia rivillä 4 - se on täysin erillinen objekti tilassa olevasta objektista.

Muista täyttää Object.ignment, koska sitä ei tueta IE: ssä eikä Babel transletoi sitä. Neljä harkittavaa vaihtoehtoa:

  1. object-assign
  2. MDN-asiakirjat
  3. Babelin polyfill
  4. Polyfill.io

Lähestymistapa # 2: Object Spread

Kohteen leviäminen on tällä hetkellä vaiheen 3 ominaisuus, ja Babel voi siirtää sen. Tämä lähestymistapa on suppeampi:

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

Rivillä 3 sanon "Käytä kaikkia tämän.state.user-sivuston ominaisuuksia uuden objektin luomiseen ja aseta sitten [nimi] -ominaisuudeksi uusi arvo, joka välitetään tapahtumalle.target.value". Joten tämä lähestymistapa toimii samalla tavalla kuin Object.ignment-lähestymistapa, mutta sillä on kaksi etua:

  1. Polyfill-täytettä ei tarvita, koska Babel voi leviää
  2. Lyhyempi

Voit jopa käyttää hajotusta ja upottamista tehdäksesi tästä yhden linjan:

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

Hajotan tapahtumaa metodin allekirjoituksessa saadakseni viittauksen tapahtumaan.target. Sitten vakuutan, että tilaksi tulisi asettaa kopio tästä.state.userista, ja asianomaisella ominaisuudella olisi uusi arvo. Pidän kuinka tiukka tämä on. Tämä on tällä hetkellä suosikkini lähestymistapa muutosten käsittelijöiden kirjoittamiseen. ?

Nämä kaksi yllä olevaa lähestymistapaa ovat yleisimpiä ja suoria tapoja käsitellä muuttumatonta tilaa. Haluatko lisää voimaa? Katso kaksi muuta vaihtoehtoa alla.

Lähestymistapa # 3: Muuttamattomuuden auttaja

Immutability-helper on kätevä kirjasto tietojen kopion muokkaamiseksi muuttamatta lähdettä. Tätä kirjastoa ehdotetaan Reactin asiakirjoissa.

// 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}); }

Linjalla 5 kutsun yhdistämistä, joka on yksi monista muuttumattomuus-auttaja-komennoista. Aivan kuten Object.assign, välitän sille kohdeobjektin ensimmäisenä parametrina ja määritän sitten ominaisuuden, johon haluaisin sulautua.

Muuttumattomuusapulainen on paljon enemmän kuin tämä. Se käyttää MongoDB: n kyselykielen inspiroimaa syntaksia ja tarjoaa useita tehokkaita tapoja työskennellä muuttumattomien tietojen kanssa.

Lähestymistapa # 4: Immutable.js

Haluatko järjestelmällisesti valvoa muuttumattomuutta? Harkitse immutable.js. Tämä kirjasto tarjoaa muuttumattomia tietorakenteita.

Tässä on esimerkki muuttumattomasta kartasta:

 // 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}); }

Edellä on kolme perusvaihetta:

  1. Tuonti muuttumaton.
  2. Aseta tila muuttumattomaan karttaan konstruktorissa
  3. Luo uusi kopio käyttäjältä muutoskäsittelijällä asetetulla menetelmällä.

Muuttamattoman.js: n kauneus: Jos yrität muunnella tilaa suoraan, se epäonnistuu . Muilla yllä olevilla lähestymistavoilla se on helppo unohtaa, eikä React varoita sinua, kun mutatoit tila suoraan.

Muuttamattomien haittapuolet?

  1. Paisua . Immutable.js lisää 57K minisoidun pakettiisi. Kun otetaan huomioon, että Preactin kaltaiset kirjastot voivat korvata Reactin vain 3K: ssa, sitä on vaikea hyväksyä.
  2. Syntaksi . Objektin ominaisuuksiin on viitattava merkkijonojen ja metodikutsujen kautta suoraan. Pidän parempana käyttäjänimi.käyttäjä.käyttäjä.nimi ('nimi').
  3. YATTL (vielä yksi opittava asia) - Tiimisi jäseneksi joutuvien on opittava vielä yksi sovellusliittymä tietojen saamiseksi ja asettamiseksi sekä uusi joukko tietotyyppejä.

Pari muuta mielenkiintoista vaihtoehtoa:

  • saumattomasti muuttumaton
  • reagoi-kopioi-kirjoita

Varoitus: Varo sisäkkäisiä esineitä!

Yllä olevat vaihtoehdot 1 ja 2 (Objektin määritys ja Object spread) tekevät vain matalan kloonin. Joten jos objektisi sisältää sisäkkäisiä objekteja, ne sisäkkäiset objektit kopioidaan viitteenä arvon sijaan . Joten jos vaihdat sisäkkäisen objektin, mutatoit alkuperäisen objektin. ?

Ole kirurginen siitä, mitä kloonaat. Älä kloonaa kaikkia asioita. Kloonaa muuttuneet objektit. Muuttamattomuus-auttaja (mainittu edellä) tekee siitä helppoa. Kuten myös vaihtoehdot, kuten immer, updeep tai pitkä luettelo vaihtoehdoista.

Saatat olla kiusaus tavoittaa syvällisiä sulautuvia työkaluja, kuten kloonisyvyys tai lodash.merge, mutta vältä sokeasti syvää kloonausta .

Tästä syystä:

  1. Syvä kloonaus on kallista.
  2. Syvä kloonaus on tyypillisesti tuhlaavaa (kloonaa vain sen, mikä on todella muuttunut)
  3. Syvä kloonaus aiheuttaa tarpeettomia renderöinteja, koska Reactin mielestä kaikki on muuttunut, vaikka itse asiassa ehkä vain tietty lapsiobjekti on muuttunut.

Kiitos Dan Abramoville edellä mainitsemistani ehdotuksista:

En usko, että cloneDeep () on hyvä suositus. Se voi olla erittäin kallista. Kopioi vain tosiasiallisesti muuttuneet osat. Kirjastot, kuten muuttumattomuus-auttaja (//t.co/YadMmpiOO8), updeep (//t.co/P0MzD19hcD) tai immer (//t.co/VyRa6Cd4IP), auttavat tässä.

- Dan Abramov (@dan_abramov) 23. huhtikuuta 2018

Viimeinen vinkki: Harkitse Functional setStaten käyttöä

Yksi toinen ryppy voi purra sinua:

setState () ei mutatoi tätä välittömästi. valtio, mutta luo odottavan tilasiirtymän. Pääsy this.state-tilaan tämän menetelmän kutsumisen jälkeen voi mahdollisesti palauttaa nykyisen arvon.

Koska setState-puhelut ovat eräajoitettuja, tällainen koodi johtaa virheeseen:

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 kirjoittanut useita kursseja JavaScriptistä, Reactista, puhtaasta koodista, .NETistä ja muusta Pluralsightista. Hän on pääkonsultti osoitteessa reactjsconsulting.com, VinSolutionsin ohjelmistoarkkitehti, Microsoft MVP, ja kouluttaa ohjelmistokehittäjiä kansainvälisesti ohjelmistokäytännöistä, kuten käyttöliittymäkehitys ja puhdas koodaus. Cory twiitti JavaScriptiä ja käyttöliittymäkehitystä Twitterissä nimellä @housecor.