Funkcionalni setState je budućnost React-a

Ažuriranje: Održao sam nastavak govora o ovoj temi na React Rallyju. Iako je ovaj post je više o „funkcionalna setState” uzorak, razgovor je više o razumijevanju setState duboko

React je popularizirao funkcionalno programiranje u JavaScript-u. To je dovelo do divovskih okvira koji su usvojili uzorak korisničkog sučelja temeljenog na komponentama koji React koristi. A sada se funkcionalna groznica prelijeva u ekosustav web razvoja uopće.

No, React tim daleko je od popuštanja. Oni nastavljaju kopati dublje, otkrivajući još funkcionalnije dragulje skrivene u legendarnoj knjižnici.

Stoga vam danas otkrivam novo funkcionalno zlato zakopano u Reactu, najbolje čuvano React u tajnosti - Funkcionalni setState!

Dobro, upravo sam izmislio to ime ... i nije posve novo ili tajna. Ne, ne baš. Vidite, to je obrazac ugrađen u React, a to je poznato samo nekolicini programera koji su se zaista udubili. I nikad nije imao ime. Ali sada je tako - Funkcionalni setState!

Idući prema riječima Dana Abramova u opisivanju ovog uzorka, Functional setState je obrazac u kojem se nalazite

"Deklarirajte promjene stanja odvojeno od klasa komponenata."

A?

Ok ... ono što već znate

React je UI knjižnica koja se temelji na komponentama. Komponenta je u osnovi funkcija koja prihvaća neka svojstva i vraća UI element.

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

Komponenta će možda morati imati i upravljati svojim stanjem. U tom slučaju komponentu obično napišete kao klasu. Tada imate njegovo stanje uživo u constructorfunkciji klase :

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

Za upravljanje državom, React nudi posebnu metodu tzv setState(). Koristite ga ovako:

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

Primijetite kako setState()funkcionira. Predajete mu objekt koji sadrži dijelove stanja koje želite ažurirati. Drugim riječima, objekt koji prosljeđujete imao bi ključeve koji odgovaraju ključevima u stanju komponente, a zatim setState()ažurira ili postavlja stanje spajanjem objekta u stanje. Dakle, "postavljena država".

Ono što vjerojatno niste znali

Sjećate se kako smo rekli da setState()djeluje? Pa, što ako bih vam rekao da umjesto prosljeđivanja objekta možete proslijediti funkciju ?

Da. setState()također prihvaća funkciju. Funkcija prihvaća prethodno stanje i trenutne rekvizite komponente koje koristi za izračunavanje i vraćanje sljedećeg stanja. Pogledajte dolje:

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

Imajte na umu da setState()je to funkcija, a mi joj prenosimo drugu funkciju (funkcionalno programiranje ... funkcionalni setState ). Na prvi pogled ovo bi moglo izgledati ružno, previše koraka samo da bi se postavilo stanje. Zašto ćete to ikada poželjeti?

Zašto prenositi funkciju na setState?

Stvar je u tome što su ažuriranja stanja možda asinkrona.

Razmislite što se događa kad setState()se pozove. React će prvo spojiti objekt u koji ste prešli setState()u trenutno stanje. Tada će započeti tu stvar s pomirenjem . Stvorit će novo stablo React Elementa (predstavljanje objekta vašeg korisničkog sučelja), razlikovati novo stablo prema starom stablu, shvatiti što se promijenilo na temelju objekta kojem ste proslijedili setState(), a zatim konačno ažurirati DOM.

Joj! Toliko posla! Zapravo je ovo čak i previše pojednostavljeni sažetak. Ali vjerujte u React!

React nije jednostavno "postavljeno stanje".

Zbog opsega posla, pozivanje setState()možda neće odmah ažurirati vaše stanje.

React može grupirati više setState()poziva u jedno ažuriranje radi izvedbe.

Što React pod tim podrazumijeva?

Prvo, " višestruki setState()pozivi" mogu značiti pozivanje setState()unutar jedne funkcije više puta, ovako:

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

Sad kad React naiđe na " višestruke setState()pozive", umjesto da to "postavljeno stanje" radi cijela tri puta , React će izbjeći tu ogromnu količinu posla koju sam gore opisao i pametno si reći: "Ne! Neću se penjati na ovu planinu tri puta, noseći i ažurirajući neki dio države na svakom pojedinačnom putovanju. Ne, radije bih uzeo spremnik, spakirao sve ove kriške i obavio ovo ažuriranje samo jednom. " I to je, prijatelji mojišaržiranje !

Imajte na umu da je ono što prelazite setState()običan objekt. Sada, pretpostavimo da React u bilo kojem trenutku nailazi na „ višestruke setState()pozive“, on izvršava batching izdvajanjem svih objekata proslijeđenih svakom setState()pozivu, spaja ih zajedno da bi formirao jedan objekt, a zatim koristi taj pojedinačni objekt za obavljanje setState().

U JavaScriptu objekti za spajanje mogu izgledati otprilike ovako:

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

Ovaj uzorak poznat je pod nazivom kompozicija predmeta.

U JavaScript-u način "spajanja" ili sastavljanja objekata funkcionira: ako tri objekta imaju iste ključeve, vrijednost ključa posljednjeg objekta koji je proslijeđen Object.assign()pobjeđuje. Na primjer:

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?

To znači da će, s obzirom na našu increaseScoreBy3gornju funkciju, konačni rezultat funkcije biti samo 1 umjesto 3, jer React nije odmah ažurirao stanje redom kojim smo pozvali setState(). Ali prvo je React sastavio sve objekte zajedno, što rezultira ovim:, a {score : this.state.score + 1}zatim je samo jednom postavio "set-state" - s novokomponiranim objektom. Nešto ovako: User.setState({score : this.state.score + 1}.

Da budemo super jasni, setState()ovdje nije problem prosljeđivanje objekta . Pravi je problem prosljeđivanje objekta setState()kada želite izračunati sljedeće stanje iz prethodnog stanja. Prestani to raditi. Nije sigurno!

Zato this.propsi this.statemože se ažurirati asinkrono, da ne bi trebali oslanjati na njihove vrijednosti za izračun sljedeće stanje.

Evo olovke Sophia Shoemaker koja demonstrira ovaj problem. Poigrajte se s tim i obratite pažnju na loša i dobra rješenja u ovoj olovci:

Funkcionalni setStavite u pomoć

Ako niste proveli vrijeme igrajući se gornjom olovkom, toplo vam preporučujem, jer će vam to pomoći da shvatite ključni koncept ovog posta.

Dok ste se igrali gornjom olovkom, nesumnjivo ste vidjeli da je funkcionalni setState riješio naš problem. Ali kako, točno?

Posavjetujmo se s Oprah od React - Dan.

Zabilježite odgovor koji je dao. Kada radite funkcionalni setState ...

Ažuriranja će se stavljati u red čekanja i kasnije izvršavati prema redoslijedu u kojem su pozvana.

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.

Happy Coding!