Руковање државом у реакцији: четири непроменљива приступа која треба размотрити

Можда најчешћа тачка забуне у Реацт-у данас: држава.

Замислите да имате образац за уређивање корисника. Уобичајено је да се креира један обрађивач промена за обраду промена у свим пољима обрасца. То може изгледати отприлике овако:

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

Забринутост је на линији 4. Линија 4 заправо мутира стање јер је корисничка променљива референца на стање. Стање реакције треба третирати као непроменљиво.

Из докумената Реацт:

Никада немојте this.stateдиректно мутирати , јер позивање setState()касније може заменити мутацију коју сте направили. Третирајте се this.stateкао да је непроменљиво.

Зашто?

  1. сетСтате пакети раде иза сцене. То значи да се ручна мутација стања може поништити када се обради сетСтате.
  2. Ако декларишете методу схоулдЦомпонентУпдате, унутра не можете користити === проверу једнакости јер се референца објекта неће променити . Дакле, горњи приступ такође има потенцијални утицај на перформансе.

Поента : Горњи пример често ради у реду, али да би се избегло ивице случајевима, третира државе као непроменљива.

Ево четири начина да се држава третира као непроменљива:

Приступ бр. 1: Објект. доделити

Објецт.ассигн ствара копију објекта. Први параметар је циљ, а затим одредите један или више параметара за својства која желите да додирнете. Дакле, поправљање горњег примера укључује једноставну промену у ред 3:

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

У 3. реду кажем „Направите нови празан објекат и додајте му сва својства на тхис.стате.усер.“ Ово ствара засебну копију корисничког објекта који се чува у стању. Сада сам сигуран да мутирам кориснички објекат на линији 4 - то је потпуно одвојен објекат од објекта у стању.

Обавезно полификујте Објецт.ассигн јер није подржан у ИЕ-у и није га пребацио Бабел. Четири опције које треба размотрити:

  1. објект-доделити
  2. Документи МДН
  3. Бабел Полифилл
  4. Полифилл.ио

Приступ бр. 2: Ширење објеката

Ширење предмета је тренутно функција 3. фазе и може га превести Бабел. Овај приступ је сажетији:

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

У 3. реду кажем „Користите сва својства тхис.стате.усер да креирате нови објекат, а затим поставите својство представљено [наме] на нову вредност прослеђену на евент.таргет.валуе“. Дакле, овај приступ делује слично приступу Објецт.ассигн, али има две предности:

  1. Није потребно полифил, јер Бабел може транспиловати
  2. Концизније

Можете чак користити деструктурирање и уградњу да бисте ово учинили једнослојном:

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

Деструктуришем догађај у потпису методе да бих добио референцу на евент.таргет. Тада изјављујем да би то стање требало поставити на копију тхис.стате.усер са релевантним својством постављеним на нову вредност. Свиђа ми се колико је ово кратко. Ово је тренутно мој омиљени приступ писању руковаоца променама. ?

Ова два горе наведена приступа су најчешћи и најједноставнији начини за руковање непроменљивим стањем. Желите више снаге? Погледајте друге две опције у наставку.

Приступ бр. 3: Помоћник за непроменљивост

Иммутабилити-хелпер је корисна библиотека за мутирање копије података без промене извора. Ова библиотека је предложена у Реацт-овим документима.

// 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 зовем спајање, што је једна од многих команди које пружа непроменљиви помоћник. Слично као Објецт.ассигн, прослеђујем му циљни објекат као први параметар, а затим одредим својство у које бих желео да се стопим.

Помоћник за непроменљивост има много више од овога. Користи синтаксу инспирисану МонгоДБ-овим језиком упита и нуди низ моћних начина за рад са непроменљивим подацима.

Приступ бр. 4: Иммутабле.јс

Желите да програмски примените непроменљивост? Размислите о непроменљивом.јс. Ова библиотека пружа непроменљиве структуре података.

Ево примера, користећи непроменљиву мапу:

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

Три су основна корака горе:

  1. Увоз непроменљив.
  2. Поставите стање на непроменљиву мапу у конструктору
  3. Користите методу сет у обрађивачу промена да бисте креирали нову копију корисника.

Лепота непроменљивог.јс: Ако покушате директно да мутирате стање, оно неће успети . Са осталим горе наведеним приступима, лако је заборавити, а Реацт вас неће упозорити када директно мутирате стање.

Лоше стране непроменљивог?

  1. Надимање . Иммутабле.јс додаје 57К умањених датотека у ваш пакет. С обзиром да библиотеке попут Преацт-а могу заменити Реацт у само 3К, то је тешко прихватити.
  2. Синтакса . Морате да референцирате својства објекта путем стрингова и позива метода уместо директно. Више волим усер.наме од усер.гет ('наме').
  3. ИАТТЛ (Још једна ствар коју треба научити) - Свако ко се придружи вашем тиму мора да научи још један АПИ за добијање и подешавање података, као и нови скуп типова података.

Још неколико занимљивих алтернатива које треба размотрити:

  • бешавно-непроменљив
  • реаговати-копирати-писати

Warning: Watch Out For Nested Objects!

Option #1 and #2 above (Object.assign and Object spread) only do a shallow clone. So if your object contains nested objects, those nested objects will be copied by reference instead of by value. So if you change the nested object, you’ll mutate the original object. ?

Be surgical about what you’re cloning. Don’t clone all the things. Clone the objects that have changed. Immutability-helper (mentioned above) makes that easy. As do alternatives like immer, updeep, or a long list of alternatives.

You might be tempted to reach for deep merging tools like clone-deep, or lodash.merge, but avoid blindly deep cloning.

Here’s why:

  1. Deep cloning is expensive.
  2. Deep cloning is typically wasteful (instead, only clone what has actually changed)
  3. Дубоко клонирање узрокује непотребне приказе, јер Реацт мисли да се све променило, а заправо се променио само одређени подређени објекат.

Хвала Дану Абрамову на сугестијама које сам горе поменуо:

Мислим да цлонеДееп () није добра препорука. То може бити врло скупо. Копирајте само оне делове који су се заиста променили. Библиотеке попут помагача за непроменљивост (//т.цо/ИадМмпиОО8), надоградње (//т.цо/П0МзД19хцД) или иммер (//т.цо/ВиРа6Цд4ИП) помажу у томе.

- Дан Абрамов (@дан_абрамов) 23. априла 2018

Завршни савет: размислите о употреби функционалног сетСтате

Још једна бора вас може угристи:

сетСтате () не мутира одмах тхис.стате већ ствара транзицију стања на чекању. Приступање тхис.стате након позива ове методе потенцијално може вратити постојећу вредност.

Пошто се позиви сетСтате групирају, овакав код доводи до грешке:

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! ?

Цори Хоусе је аутор вишеструких курсева о ЈаваСцрипт-у, Реацт-у, чистом коду, .НЕТ-у и више о Плуралсигхт-у. Главни је консултант у компанији респонсејсцонсултинг.цом, софтверски архитекта у компанији ВинСолутионс, Мицрософт МВП, и међународне програмере обучава за софтверске праксе попут фронт-енд развоја и чистог кодирања. Цори твитује о ЈаваСцрипт-у и фронт-енд развоју на Твиттеру као @хоусецор.