Stav riešenia: štyri nezmeniteľné prístupy, ktoré je potrebné zvážiť

Možno najbežnejší bod zmätku v Reacte dnes: štát.

Predstavte si, že máte formulár na úpravu používateľa. Je bežné vytvoriť jeden obslužný program zmien na spracovanie zmien vo všetkých poliach formulára. Môže to vyzerať asi takto:

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

Obava je na riadku 4. Riadok 4 skutočne mutuje stav, pretože užívateľská premenná je odkazom na štát. Stav reakcie by sa mal považovať za nemenný.

Z dokumentov React:

Nikdy nemutujte this.statepriamo, pretože setState()následné volanie môže nahradiť vykonanú mutáciu. Správajte sa this.state, akoby to bolo nemenné.

Prečo?

  1. dávky setState pracujú v zákulisí. To znamená, že pri spracovaní setState môže byť prepísaná manuálna mutácia stavu.
  2. Ak deklarujete metódu shouldComponentUpdate, nemôžete vo vnútri použiť kontrolu rovnosti ===, pretože odkaz na objekt sa nezmení . Vyššie uvedený prístup má teda tiež potenciálny vplyv na výkon.

Zrátané a podčiarknuté : Vyššie uvedený príklad často funguje dobre, ale aby ste sa vyhli okrajovým prípadom, považujte stav za nemenný.

Tu sú štyri spôsoby, ako považovať štát za nemenný:

Prístup č. 1: Objekt. Priradenie

Object.assign vytvorí kópiu objektu. Prvým parametrom je cieľ, potom určíte jeden alebo viac parametrov pre vlastnosti, ktoré chcete použiť. Oprava vyššie uvedeného príkladu teda zahŕňa jednoduchú zmenu v riadku 3:

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

V riadku 3 hovorím „Vytvorte nový prázdny objekt a pridajte doň všetky vlastnosti tohto súboru.user.“ Takto sa vytvorí samostatná kópia používateľského objektu, ktorá je uložená v stave. Teraz môžem bezpečne mutovať objekt používateľa na riadku 4 - je to úplne samostatný objekt od objektu v stave.

Nezabudnite polyfill Object.assign, pretože v IE nie je podporovaný a Babel ho neprepracoval. Je potrebné zvážiť štyri možnosti:

  1. priradiť objekt
  2. Dokument MDN
  3. Babel Polyfill
  4. Polyfill.io

Prístup č. 2: Spread objektov

Rozširovanie objektov je v súčasnosti funkciou fázy 3 a môže ich transponovať Babel. Tento prístup je stručnejší:

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

Na riadku 3 hovorím „Použite všetky vlastnosti na this.state.user na vytvorenie nového objektu a potom nastavte vlastnosť predstavovanú [name] na novú hodnotu odovzdanú event.target.value“. Tento prístup teda funguje podobne ako prístup Object.assign, ale má dve výhody:

  1. Polyfill nie je potrebný, pretože Babel sa môže prihadzovať
  2. Stručnejšie

Môžete dokonca použiť deštrukturalizáciu a vložku, aby ste z toho spravili jeden riadok:

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

Destrukujem udalosť v podpise metódy, aby som získal odkaz na event.target. Potom deklarujem, že tento stav by mal byť nastavený na kópiu this.state.user s príslušnou vlastnosťou nastavenou na novú hodnotu. Páči sa mi, aké je to strohé. Toto je momentálne môj obľúbený prístup k písaniu obslužných rutín zmien. ?

Tieto dva prístupy uvedené vyššie sú najbežnejšími a najpriamejšími spôsobmi riešenia nezmeniteľného stavu. Chcete viac energie? Nižšie si pozrite ďalšie dve možnosti.

Prístup č. 3: Pomocník pre nezmeniteľnosť

Immutability-helper je šikovná knižnica na mutovanie kópie dát bez zmeny zdroja. Táto knižnica je navrhnutá v dokumentoch React.

// 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 linke 5 volám merge, čo je jeden z mnohých príkazov poskytovaných pomocníkom nemennosti. Rovnako ako Object.assign, odovzdám mu cieľový objekt ako prvý parameter a potom špecifikujem vlastnosť, do ktorej by som sa chcel zlúčiť.

Pomocník v nemennosti má oveľa viac ako toto. Používa syntax inšpirovanú dotazovacím jazykom MongoDB a ponúka celý rad výkonných spôsobov práce s nemennými údajmi.

Prístup č. 4: Immutable.js

Chcete programovo vynútiť nemennosť? Zvážte immutable.js. Táto knižnica poskytuje nemenné dátové štruktúry.

Tu je príklad použitia nemennej mapy:

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

Vyššie sú uvedené tri základné kroky:

  1. Dovoz nemenný.
  2. Nastaviť stav na nemennú mapu v konštruktore
  3. Na vytvorenie novej kópie používateľa použite metódu set v obslužnej rutine zmien.

Krása immutable.js: Ak sa pokúsite priamo mutovať stav, zlyhá to . Pri ostatných vyššie uvedených prístupoch je ľahké na to zabudnúť a React vás nebude varovať, keď mutujete stav priamo.

Nevýhody nemenného?

  1. Nafúknuť . Immutable.js pridá do vášho zväzku 57 000 minifikovaných súborov. Vzhľadom na to, že knižnice ako Preact môžu nahradiť React iba za 3 kB, je ťažké to prijať.
  2. Syntax . Musíte priamo odkazovať na vlastnosti objektu pomocou reťazcov a volaní metód. Preferujem user.name pred user.get ('name').
  3. YATTL (ešte niečo sa treba naučiť) - Každý, kto sa pripojí k vášmu tímu, musí sa naučiť ešte ďalšie API na získavanie a nastavovanie údajov, ako aj novú sadu dátových typov.

Zvážiť niekoľko ďalších zaujímavých alternatív:

  • bezšvové-nemenné
  • reagovať-kopírovať-napísať

Upozornenie: Pozor na vnorené objekty!

Možnosť č. 1 a 2 vyššie (Object.assign a Object spread) robia iba plytký klon. Takže ak váš objekt obsahuje vnorené objekty, budú sa tieto vnorené objekty kopírovať pomocou odkazu namiesto podľa hodnoty . Takže ak zmeníte vnorený objekt, pôvodný objekt mutujete. ?

Buďte chirurgický ohľadom toho, čo klonujete. Neklonujte všetky veci. Klonujte objekty, ktoré sa zmenili. Pomocník pre nezmeniteľnosť (uvedený vyššie) to uľahčuje. Rovnako ako alternatívy ako immer, updeep alebo dlhý zoznam alternatív.

Možno vás bude lákať siahnuť po nástrojoch na hlboké zlúčenie, ako je clone-deep alebo lodash.merge, ale vyhnite sa slepo hlbokému klonovaniu .

Tu je dôvod, prečo:

  1. Hlboké klonovanie je drahé.
  2. Hlboké klonovanie je zvyčajne zbytočné (namiesto toho klonujte iba to, čo sa skutočne zmenilo)
  3. Hlboké klonovanie spôsobuje zbytočné vykresľovanie, pretože React si myslí, že sa všetko zmenilo, hoci v skutočnosti sa zmenil snáď len konkrétny podradený objekt.

Ďakujem Danovi Abramovovi za návrhy, ktoré som spomenul vyššie:

Nemyslím si, že cloneDeep () je dobrým odporúčaním. Môže to byť veľmi drahé. Skopírujte iba tie časti, ktoré sa skutočne zmenili. Knižnice ako immutability-helper (//t.co/YadMmpiOO8), updeep (//t.co/P0MzD19hcD) alebo immer (//t.co/VyRa6Cd4IP) s tým pomáhajú.

- Dan Abramov (@dan_abramov) 23. apríla 2018

Záverečný tip: Zvážte použitie funkčného setState

Môže vás pohrýzť ešte jedna vráska:

setState () nemutuje okamžite tento stav, ale vytvára čakajúci prechod stavu. Prístup k this.state po volaní tejto metódy môže potenciálne vrátiť existujúcu hodnotu.

Pretože hovory setState sú dávkované, kód ako tento vedie k chybe:

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 je autorkou viacerých kurzov o jazykoch JavaScript, React, čistom kóde, .NET a ďalších témach o Pluralsight. Je hlavným konzultantom v spoločnosti reajsconsulting.com, softvérovým architektom vo VinSolutions, Microsoft MVP a medzinárodne trénuje vývojárov softvéru v oblasti softvérových postupov, ako je front-end vývoj a čisté kódovanie. Cory tweety o JavaScripte a front-end vývoji na Twitteri ako @housecor.