Funkcinė „setState“ yra „React“ ateitis

Atnaujinimas: Aš surengiau tolesnę diskusiją šia tema „React Rally“. Nors šiame įraše daugiau kalbama apie „funkcinį„ setState “modelį, kalbama daugiau apie„ setState “ gilų supratimą

„React“ išpopuliarino funkcinį programavimą „JavaScript“. Tai paskatino milžiniškus pagrindus pritaikyti komponentais pagrįstą vartotojo sąsają, kurią naudoja „React“. Dabar funkcinė karštinė plinta į interneto kūrimo ekosistemą.

Tačiau „React“ komanda toli gražu nenusileidžia. Jie ir toliau gilinasi, atranda dar funkcionalesnius brangakmenius, paslėptus legendinėje bibliotekoje.

Taigi šiandien aš jums atskleidžiu naują funkcinį auksą, užkastą „ React“, geriausiai saugomą „React“ paslaptį - „ Functional setState“!

Gerai, aš ką tik sugalvojau tą vardą ... ir jis nėra visiškai naujas ar paslaptis. Ne, ne tiksliai. Žiūrėk, tai yra „React“ įmontuotas šablonas, kurį žino tik keli kūrėjai, kurie tikrai įsigilino. Ir niekada neturėjo vardo. Bet dabar taip yra - funkcionalus „setState“!

Remiantis Dano Abramovo žodžiais apibūdinant šį modelį, „ Functional setState“ yra modelis, kuriame jūs

„Deklaruoti būsenos pokyčius atskirai nuo komponentų klasių.“

Huh?

Gerai ... ką jau žinai

„React“ yra komponentų pagrindu veikianti vartotojo sąsajos biblioteka. Komponentas iš esmės yra funkcija, kuri priima kai kurias savybes ir grąžina vartotojo sąsajos elementą.

function User(props) { return ( A pretty user );}

Komponentui gali reikėti turėti ir valdyti jo būseną. Tokiu atveju komponentą paprastai rašote kaip klasę. Tada jūs turite savo būseną gyvai pagal klasės constructorfunkciją:

class User { constructor () { this.state = { score : 0 }; }
 render () { return ( This user scored {this.state.score} ); }}

Norėdami valdyti būseną, „React“ pateikia specialų metodą, vadinamą setState(). Jūs jį naudojate taip:

class User { ... 
 increaseScore () { this.setState({score : this.state.score + 1}); }
 ...}

Atkreipkite dėmesį, kaip tai setState()veikia. Perduodate jam objektą, kuriame yra būsenos (-ų) dalis, kurią norite atnaujinti. Kitaip tariant, jūsų perduotas objektas turėtų raktus, atitinkančius raktus komponento būsenoje, tada setState()atnaujina arba nustato būseną sujungdamas objektą su būsena. Taigi, „nustatyta valstybė“.

Ko tikriausiai nežinojai

Pamenate, kaip mes sakėme, setState()veikia? Na, o jei aš tau pasakyčiau, kad užuot perdavęs objektą, galėtum perduoti funkciją ?

Taip. setState()taip pat priima funkciją. Funkcija priima ankstesnę būseną ir esamus komponento rekvizitus, kuriuos ji naudoja apskaičiuoti ir grąžinti kitą būseną. Žiūrėkite žemiau:

this.setState(function (state, props) { return { score: state.score - 1 }});

Atkreipkite dėmesį, kad setState()tai yra funkcija, ir mes jai perduodame kitą funkciją (funkcinis programavimas ... funkcinis setState ). Iš pirmo žvilgsnio tai gali atrodyti negražu, per daug žingsnių, kad tik būtų nustatyta. Kodėl kada nors norėsite tai padaryti?

Kodėl reikia perduoti funkciją setState?

Reikalas tas, kad būsenos atnaujinimai gali būti asinchroniški.

Pagalvokite, kas nutinka, kai setState()iškviečiama. „React“ pirmiausia sujungs objektą, kuriam perdavėte, setState()į dabartinę būseną. Tada prasidės tas susitaikymo dalykas. Tai sukurs naują „React Element“ medį (jūsų vartotojo sąsajos objekto atvaizdavimas), difuzuos naują medį su senu medžiu, išsiaiškins, kas pasikeitė pagal objektą, kuriam perdavėte setState(), tada galiausiai atnaujinsite DOM.

Oi! Tiek darbo! Tiesą sakant, tai netgi pernelyg supaprastinta santrauka. Bet pasitikėk React!

„React“ nėra tiesiog „nustatytoji būsena“.

Dėl darbo kiekio dalyvauja, tad setState()gali ne iš karto atnaujinti savo būseną.

„Reaguoti“ gali paketuoti kelis setState()skambučius į vieną atnaujinimą, kad būtų užtikrintas našumas.

Ką tai reiškia „React“?

Pirma, „ keli setState()skambučiai“ gali reikšti skambučius setState()vienoje funkcijoje daugiau nei vieną kartą:

...
state = {score : 0};
// multiple setState() callsincreaseScoreBy3 () { this.setState({score : this.state.score + 1}); this.setState({score : this.state.score + 1}); this.setState({score : this.state.score + 1});}
...

Dabar, kai „React“ susiduria su „ keliais setState()skambučiais“, užuot tris kartus atlikęs „nustatytą būseną“ , „React“ išvengs to didžiulio darbo, kurį aprašiau aukščiau, ir pats sau protingai pasakys: „Ne! Aš neketinu lipti į šį kalną tris kartus, nešiodama ir atnaujindama valstijos gabalėlį kiekvienoje kelionėje. Ne, aš norėčiau įsigyti konteinerį, supakuoti visus šiuos gabalėlius ir atlikti šį atnaujinimą tik vieną kartą “. Taip yra, mano draugaipartija !

Atminkite, kad tai, ką jūs perduodate, setState()yra paprastas objektas. Tarkime, bet kada, kai „React“ susiduria su „ keliais setState()skambučiais“, jis atlieka paketinį procesą išskirdamas visus kiekvienam setState()skambučiui perduotus objektus , sujungdamas juos į vieną objektą, tada naudoja tą vienintelį objektą setState().

„JavaScript“ sujungti objektai gali atrodyti maždaug taip:

const singleObject = Object.assign( {}, objectFromSetState1, objectFromSetState2, objectFromSetState3);

Šis modelis yra žinomas kaip objekto kompozicija.

„Java“ sistemoje objektų „sujungimas“ ar komponavimas veikia taip: jei trys objektai turi tuos pačius raktus, laimi paskutinio perduoto objekto rakto vertė Object.assign(). Pavyzdžiui:

const me = {name : "Justice"}, you = {name : "Your name"}, we = Object.assign({}, me, you);
we.name === "Your name"; //true
console.log(we); // {name : "Your name"}

Because you are the last object merged into we, the value of name in the you object — “Your name” — overrides the value of name in the me object. So “Your name” makes it into the we object… you win! :)

Thus, if you call setState() with an object multiple times — passing an object each time — React will merge. Or in other words, it will compose a new object out of the multiple objects we passed it. And if any of the objects contains the same key, the value of the key of the last object with same key is stored. Right?

Tai reiškia, kad atsižvelgiant į increaseScoreBy3aukščiau pateiktą mūsų funkciją, galutinis funkcijos rezultatas bus tik 1, o ne 3, nes „React“ ne iš karto atnaujino būseną tokia tvarka, kokia mes skambinome setState(). Bet pirmiausia „React“ sudarė visus objektus kartu, o tai lemia: {score : this.state.score + 1}tada tik vieną kartą „nustatė būseną“ - su naujai sukomponuotu objektu. Kažkas panašaus į tai: User.setState({score : this.state.score + 1}.

Kad būtų aišku, objekto perdavimas setState()nėra čia problema. Tikroji problema yra objekto perdavimas, setState()kai norite apskaičiuoti kitą būseną iš ankstesnės būsenos. Taigi nustokite tai daryti. Tai nėra saugu!

Nes this.propsir this.stategali būti atnaujintas asinchroniškai, jūs neturėtumėte pasikliauti savo vertybes apskaičiavimo kitą valstybę.

Čia yra Sophia Shoemaker rašiklis, kuris demonstruoja šią problemą. Žaisk su juo ir atkreipk dėmesį į blogus ir į gerus šio rašiklio sprendimus:

Funkcinis rinkinys gelbsti valstybę

Jei nepraleidote laiko žaisdami aukščiau esančiu rašikliu, primygtinai rekomenduoju tai padaryti, nes tai padės suprasti pagrindinę šio įrašo idėją.

Žaisdami aukščiau esančiu rašikliu, be abejo, matėte, kad funkcinė „setState“ išsprendė mūsų problemą. Bet kaip tiksliai?

Pasikonsultuokime su Reakcijos Oprah - Dan.

Atkreipkite dėmesį į jo duotą atsakymą. Kai atliksite funkcinę „setState“…

Atnaujinimai bus įtraukti į eilę ir vėliau vykdomi tokia tvarka, kokia jiems buvo paskambinta.

So, when React encounters “multiple functional setState() calls” , instead of merging objects together, (of course there are no objects to merge) React queues the functions “in the order they were called.”

After that, React goes on updating the state by calling each functions in the “queue”, passing them the previous state — that is, the state as it was before the first functional setState() call (if it’s the first functional setState() currently executing) or the state with the latest update from the previous functional setState() call in the queue.

Again, I think seeing some code would be great. This time though, we’re gonna fake everything. Know that this is not the real thing, but is instead just here to give you an idea of what React is doing.

Also, to make it less verbose, we’ll use ES6. You can always write the ES5 version later if you want.

First, let’s create a component class. Then, inside it, we’ll create a fake setState() method. Also, our component would have a increaseScoreBy3()method, which will do a multiple functional setState. Finally, we’ll instantiate the class, just as React would do.

class User{ state = {score : 0};
 //let's fake setState setState(state, callback) { this.state = Object.assign({}, this.state, state); if (callback) callback(); }
 // multiple functional setState call increaseScoreBy3 () { this.setState( (state) => ({score : state.score + 1}) ), this.setState( (state) => ({score : state.score + 1}) ), this.setState( (state) => ({score : state.score + 1}) ) }}
const Justice = new User();

Note that setState also accepts an optional second parameter — a callback function. If it’s present React calls it after updating the state.

Now when a user triggers increaseScoreBy3(), React queues up the multiple functional setState. We won’t fake that logic here, as our focus is on what actually makes functional setState safe. But you can think of the result of that “queuing” process to be an array of functions, like this:

const updateQueue = [ (state) => ({score : state.score + 1}), (state) => ({score : state.score + 1}), (state) => ({score : state.score + 1})];

Finally, let’s fake the updating process:

// recursively update state in the orderfunction updateState(component, updateQueue) { if (updateQueue.length === 1) { return component.setState(updateQueue[0](component.state)); }
return component.setState( updateQueue[0](component.state), () => updateState( component, updateQueue.slice(1)) );}
updateState(Justice, updateQueue);

True, this is not as so sexy a code. I trust you could do better. But the key focus here is that every time React executes the functions from your functional setState, React updates your state by passing it a fresh copy of the updated state. That makes it possible for functional setState to set state based on the previous state.

Here I made a bin with the complete code. Tinker around it (possibly make it look sexier), just to get more sense of it.

FunctionalSetStateInAction

A Play with the code in this bin will be fun. Remember! we’re just faking React to get the idea...jsbin.com

Play with it to grasp it fully. When you come back we’re gonna see what makes functional setState truly golden.

The best-kept React secret

So far, we’ve deeply explored why it’s safe to do multiple functional setStates in React. But we haven’t actually fulfilled the complete definition of functional setState: “Declare state changes separately from the component classes.”

Over the years, the logic of setting-state — that is, the functions or objects we pass to setState() — have always lived inside the component classes. This is more imperative than declarative.

Well today, I present you with newly unearthed treasure — the best-kept React secret:

Thanks to Dan Abramov!

That is the power of functional setState. Declare your state update logic outside your component class. Then call it inside your component class.

// outside your component classfunction increaseScore (state, props) { return {score : state.score + 1}}
class User{ ...
// inside your component class handleIncreaseScore () { this.setState( increaseScore) }
 ...}

This is declarative! Your component class no longer cares how the state updates. It simply declares the type of update it desires.

To deeply appreciate this, think about those complex components that would usually have many state slices, updating each slice on different actions. And sometimes, each update function would require many lines of code. All of this logic would live inside your component. But not anymore!

Also, if you’re like me, I like keeping every module as short as possible, but now you feel like your module is getting too long. Now you have the power to extract all your state change logic to a different module, then import and use it in your component.

import {increaseScore} from "../stateChanges";
class User{ ...
 // inside your component class handleIncreaseScore () { this.setState( increaseScore) }
 ...}

Now you can even reuse the increaseScore function in a different component. Just import it.

What else can you do with functional setState?

Make testing easy!

You can also pass extra arguments to calculate the next state (this one blew my mind… #funfunFunction).

Expect even more in…

The Future of React

For years now, the react team has been experimenting with how to best implement stateful functions.

Functional setState seems to be just the right answer to that (probably).

Hey, Dan! Any last words?

If you’ve made it this far, you’re probably as excited as I am. Start experimenting with this functional setStatetoday!

If you feel like I’ve done any nice job, or that others deserve a chance to see this, kindly click on the green heart below to help spread a better understanding of React in our community.

If you have a question that hasn’t been answered or you don’t agree with some of the points here feel free to drop in comments here or via Twitter.

Laimingo kodavimo!