Funktionel setState er fremtiden for React

Opdatering: Jeg holdt en opfølgningstale om dette emne på React Rally. Mens dette indlæg handler mere om det "funktionelle setState" -mønster, handler snakket mere om at forstå setState dybt

React har populariseret funktionel programmering i JavaScript. Dette har ført til kæmpe rammer, der vedtager det komponentbaserede UI-mønster, som React bruger. Og nu sprøjter funktionel feber over i webudviklingsøkosystemet generelt.

Men React-teamet er langt fra at give afkald på det. De fortsætter med at grave dybere og opdager endnu mere funktionelle ædelstene skjult i det legendariske bibliotek.

Så i dag afslører jeg et nyt funktionelt guld begravet i React, bedst bevarede React hemmelighed - Funktionel setState!

Okay, jeg har lige lavet det navn ... og det er ikke helt nyt eller en hemmelighed. Nej, ikke ligefrem. Se, det er et mønster indbygget i React, det er kun kendt af få udviklere, der virkelig har dybt dybt. Og det havde aldrig noget navn. Men nu gør det det - Funktionel setState!

Ved at følge Dan Abramovs ord i beskrivelsen af ​​dette mønster er Functional setState et mønster, hvor du

"Erklær tilstandsændringer separat fra komponentklasserne."

Hvad?

Okay ... hvad du allerede ved

React er et komponentbaseret UI-bibliotek. En komponent er dybest set en funktion, der accepterer nogle egenskaber og returnerer et UI-element.

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

En komponent skal muligvis have og styre dens tilstand. I så fald skriver du normalt komponenten som en klasse. Så har du sin tilstand live i klassefunktionen constructor:

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

For at administrere staten giver React en speciel metode kaldet setState(). Du bruger det sådan:

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

Bemærk hvordan setState()fungerer. Du sender det et objekt, der indeholder dele af den tilstand, du vil opdatere. Med andre ord, det objekt, du sender, vil have nøgler, der svarer til nøglerne i komponenttilstand, og derefter setState()opdaterer eller indstiller tilstanden ved at flette objektet til staten. Således "sæt-stat".

Hvad du sandsynligvis ikke vidste

Kan du huske, hvordan vi sagde setState()fungerer? Nå, hvad hvis jeg fortalte dig, at i stedet for at passere et objekt, kunne du bestå en funktion ?

Ja. setState()accepterer også en funktion. Funktionen accepterer den tidligere tilstand og aktuelle rekvisitter for den komponent, som den bruger til at beregne og returnere den næste tilstand. Se det nedenfor:

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

Bemærk, at det setState()er en funktion, og vi overfører en anden funktion til den (funktionel programmering ... funktionel setState ). Ved første øjekast kan dette virke grimt, for mange trin bare for at sætte-tilstand. Hvorfor vil du nogensinde ønske at gøre dette?

Hvorfor overføre en funktion til setState?

Sagen er, tilstandsopdateringer kan være asynkrone.

Tænk over, hvad der sker, når setState()der kaldes. React fletter først det objekt, du har sendt til, setState()i den aktuelle tilstand. Så starter den forsonings ting. Det opretter et nyt React Element-træ (en objektrepræsentation af dit brugergrænseflade), diffunder det nye træ mod det gamle træ, finder ud af, hvad der er ændret baseret på det objekt, du har sendt til setState(), og opdaterer endelig DOM.

Whew! Så meget arbejde! Faktisk er dette endda en alt for forenklet sammenfatning. Men tillid til React!

React “sætter ikke” blot.

På grund af den involverede mængde arbejde opdaterer opkald setState()muligvis ikke straks din tilstand.

React kan gruppere flere setState()opkald i en enkelt opdatering med henblik på ydeevne.

Hvad betyder React med dette?

For det første kan " flere setState()opkald" betyde at ringe setState()inde i en enkelt funktion mere end en gang, sådan:

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

Når React nu støder på " flere setState()opkald" i stedet for at udføre denne "set-state" tre hele gange , vil React undgå den enorme mængde arbejde, jeg beskrev ovenfor, og siger smart til sig selv: "Nej! Jeg vil ikke bestige dette bjerg tre gange og bære og opdatere noget udsnit af staten på hver eneste tur. Nej, jeg vil hellere få en container, pakke alle disse skiver sammen og gøre denne opdatering kun en gang. ” Og det er mine vennerbatching !

Remember that what you pass to setState() is a plain object. Now, assume anytime React encounters “multiple setState() calls”, it does the batching thing by extracting all the objects passed to each setState() call, merges them together to form a single object, then uses that single object to do setState() .

In JavaScript merging objects might look something like this:

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

This pattern is known as object composition.

In JavaScript, the way “merging” or composing objects works is: if the three objects have the same keys, the value of the key of the last object passed to Object.assign() wins. For example:

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?

Det betyder, at increaseScoreBy3det endelige resultat af funktionen i betragtning af vores funktion ovenfor vil være 1 i stedet for 3, fordi React ikke straks opdaterede tilstanden i den rækkefølge, vi kaldte setState(). Men først komponerede React alle objekterne sammen, hvilket resulterede i dette {score : this.state.score + 1}:, "sætte-tilstand" derefter kun en gang - med det nyligt sammensatte objekt. Noget som dette: User.setState({score : this.state.score + 1}.

For at være superklar setState()er det ikke problemet her at sende objekt til . Det virkelige problem er at videregive objekt til setState()når du vil beregne den næste tilstand fra den forrige tilstand. Så hold op med at gøre dette. Det er ikke sikkert!

Fordi this.propsog this.statemuligvis opdateres asynkront, bør du ikke stole på deres værdier til beregning af den næste tilstand.

Her er en pen af ​​Sophia Shoemaker, der demonstrerer dette problem. Spil med det, og vær opmærksom på både de dårlige og de gode løsninger i denne pen:

Funktionelt sæt Til redning

Hvis du ikke har brugt tid på at lege med pennen ovenfor, anbefaler jeg kraftigt, at du gør det, da det hjælper dig med at forstå kernekonceptet i dette indlæg.

Mens du spillede med pennen ovenfor, så du uden tvivl, at funktionelt setState løste vores problem. Men hvordan, nøjagtigt?

Lad os konsultere Oprah of React - Dan.

Bemærk det svar, han gav. Når du laver funktionel setState ...

Opdateringer vil blive sat i kø og senere udført i den rækkefølge, de blev kaldt til.

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.

Glad kodning!