Možda najčešća točka zbrke u Reactu danas: država.
Zamislite da imate obrazac za uređivanje korisnika. Uobičajeno je stvoriti jedan rukovatelj promjenama za obradu promjena u svim poljima obrasca. To može izgledati otprilike ovako:
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}); }
Zabrinutost je na liniji 4. Linija 4 zapravo mutira stanje jer je korisnička varijabla referenca na stanje. Stanje reakcije treba tretirati kao nepromjenjivo.
Iz dokumenata React:
Nikad nemojtethis.state
izravno mutirati
, jer pozivanje
setState()
nakon toga može zamijeniti mutaciju koju ste napravili. Ponašajte se
this.state
kao da je nepromjenjiv.
Zašto?
- setState serije rade iza kulisa. To znači da se ručna mutacija stanja može nadjačati kada se obradi setState.
- Ako deklarirate metodu shouldComponentUpdate, unutra ne možete koristiti provjeru jednakosti === jer se referenca objekta neće promijeniti . Dakle, gornji pristup također ima potencijalni utjecaj na izvedbu.
Zaključak : Gornji primjer često dobro funkcionira, ali da biste izbjegli rubne slučajeve, državu tretirajte kao nepromjenjivu.
Evo četiri načina da se država tretira kao nepromjenjiva:
Pristup # 1: Objekt.dod
Object.assign stvara kopiju objekta. Prvi je parametar cilj, a zatim specificirate jedan ili više parametara za svojstva koja želite dodirnuti. Dakle, popravljanje gornjeg primjera uključuje jednostavnu promjenu retka 3:
updateState(event) { const {name, value} = event.target; let user = Object.assign({}, this.state.user); user[name] = value; return this.setState({user}); }
U retku 3 kažem "Stvorite novi prazan objekt i dodajte mu sva svojstva na this.state.user." Ovo stvara zasebnu kopiju korisničkog objekta koji je pohranjen u stanju. Sada sam siguran da mutiram korisnički objekt na liniji 4 - to je potpuno odvojen objekt od objekta u stanju.
Obavezno polifizirajte Object.assign jer nije podržan u IE-u i nije ga prebacio Babel. Četiri mogućnosti koje treba razmotriti:
- objekt-dodijeliti
- Dokumenti MDN-a
- Babel Polyfill
- Polyfill.io
Pristup br. 2: Širenje predmeta
Širenje predmeta trenutno je značajka 3. stupnja i može ga prevesti Babel. Ovaj je pristup sažetiji:
updateState(event) { const {name, value} = event.target; let user = {...this.state.user, [name]: value}; this.setState({user}); }
U retku 3 kažem „Upotrijebite sva svojstva this.state.user za stvaranje novog objekta, a zatim postavite svojstvo predstavljeno s [name] na novu vrijednost prosljeđenu event.target.value“. Dakle, ovaj pristup djeluje slično pristupu Object.assign, ali ima dvije prednosti:
- Nije potrebno polifil, jer Babel može transpilirati
- Sažetiji
Možete čak koristiti destrukturiranje i ugrađivanje kako biste ovo učinili jednoslojnom:
updateState({target}) { this.setState({user: {...this.state.user, [target.name]: target.value}}); }
Destrukturiram događaj u potpisu metode kako bih dobio referencu na event.target. Tada izjavljujem da bi to stanje trebalo postaviti na kopiju this.state.user s odgovarajućim svojstvom postavljenim na novu vrijednost. Sviđa mi se koliko je ovo kratko. Ovo je trenutno moj omiljeni pristup pisanju rukovatelja promjenama. ?
Ova dva gore navedena pristupa su najčešći i najjednostavniji načini rješavanja nepromjenjivog stanja. Želite više snage? U nastavku pogledajte ostale dvije mogućnosti.
Pristup br. 3: Pomoćnik za nepromjenjivost
Pomoćnik za nepromjenjivost korisna je knjižnica za mutiranje kopije podataka bez promjene izvora. Ova je knjižnica predložena u Reactovim dokumentima.
// 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}); }
Na liniji 5 zovem spajanje, što je jedna od mnogih naredbi koje pruža pružatelj nepromjenjivosti. Slično kao Object.assign, prosljeđujem mu ciljni objekt kao prvi parametar, a zatim odredim svojstvo u koje bih se želio spojiti.
Pomoćnik u nepromjenjivosti ima puno više od ovoga. Koristi sintaksu nadahnutu jezikom upita MongoDB-a i nudi razne moćne načine rada s nepromjenjivim podacima.
Pristup br. 4: Nepromjenjiv.js
Želite li programski provoditi nepromjenjivost? Razmislite o nepromjenjivom.js. Ova knjižnica pruža nepromjenjive strukture podataka.
Evo primjera pomoću nepromjenjive karte:
// 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}); }
Tri su osnovna koraka gore:
- Uvoz nepromjenjiv.
- Postavite stanje na nepromjenjivu kartu u konstruktoru
- Upotrijebite metodu set u obradi promjena da biste stvorili novu kopiju korisnika.
Ljepota nepromjenjivog.js: Ako pokušate izravno mutirati stanje, ono neće uspjeti . S ostalim gore navedenim pristupima lako je zaboraviti, a React vas neće upozoriti kad izravno mutirate stanje.
Loše strane nepromjenjivog?
- Nadimanje . Immutable.js vašem paketu dodaje 57 000 umanjenih datoteka. S obzirom na to da knjižnice poput Preact-a mogu zamijeniti React u samo 3K, to je teško prihvatiti.
- Sintaksa . Morate referencirati svojstva objekta putem stringova i poziva metode umjesto izravno. Više volim user.name od user.get ('ime').
- YATTL (Još jedna stvar koju treba naučiti) - Svatko tko se pridruži vašem timu mora naučiti još jedan API za dobivanje i postavljanje podataka, kao i novi skup tipova podataka.
Još nekoliko zanimljivih alternativa koje treba razmotriti:
- bešavno-nepromjenjiv
- reagirati-kopirati-pisati
Upozorenje: Pazite na ugniježđene predmete!
Opcije br. 1 i br. 2 (Object.assign i Object spread) čine samo plitki klon. Dakle, ako vaš objekt sadrži ugniježđene objekte, oni će se kopirati referencom umjesto vrijednošću . Dakle, ako promijenite ugniježđeni objekt, mutirat ćete izvorni objekt. ?
Budite kirurški u vezi s onim što klonirate. Ne kloniraj sve stvari. Klonirajte objekte koji su se promijenili. Pomoćnik za nepromjenjivost (gore spomenuti) to olakšava. Kao i alternative poput immer, updeep ili dugački popis alternativa.
Možda ćete doći u napast da posegnete za alatima za duboko spajanje, poput clone-deep ili lodash.merge, ali izbjegavajte slijepo duboko kloniranje .
Evo zašto:
- Duboko kloniranje je skupo.
- Duboko kloniranje obično je rastrošno (umjesto toga klonirajte samo ono što se zapravo promijenilo)
- Dubinsko kloniranje uzrokuje nepotrebne prikaze, jer React misli da se sve promijenilo, a zapravo se promijenio samo određeni podređeni objekt.
Hvala Danu Abramovu na prijedlozima koje sam gore spomenuo:
Mislim da cloneDeep () nije dobra preporuka. To može biti vrlo skupo. Kopirajte samo dijelove koji su se stvarno promijenili. Knjižnice poput pomagača za nepromjenjivost (//t.co/YadMmpiOO8), nadogradnje (//t.co/P0MzD19hcD) ili immer (//t.co/VyRa6Cd4IP) pomažu u tome.
- Dan Abramov (@dan_abramov) 23. travnja 2018Završni savjet: razmislite o upotrebi funkcionalnog setState
Još jedna bora vas može ugristi:
setState () ne mutira odmah this.state već stvara prijelaz stanja na čekanju. Pristup this.state nakon poziva ove metode potencijalno može vratiti postojeću vrijednost.Budući da se pozivi setState skupljaju, ovakav kod dovodi do greške:
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 is the author of multiple courses on JavaScript, React, clean code, .NET, and more on Pluralsight. He is principal consultant at reactjsconsulting.com, a Software Architect at VinSolutions, a Microsoft MVP, and trains software developers internationally on software practices like front-end development and clean coding. Cory tweets about JavaScript and front-end development on Twitter as @housecor.