Functional setState ist die Zukunft von React

Update: Ich habe auf der React Rally einen Folgevortrag zu diesem Thema gehalten. Während dieser Beitrag mehr über die „functional setState“ -Muster ist, ist die Rede mehr über Verständnis setState tief

React hat die funktionale Programmierung in JavaScript populär gemacht. Dies hat dazu geführt, dass riesige Frameworks das von React verwendete komponentenbasierte UI-Muster übernehmen. Und jetzt überträgt sich funktionelles Fieber auf das gesamte Webentwicklungs-Ökosystem.

Aber das React-Team ist weit davon entfernt, nachzulassen. Sie graben weiter tiefer und entdecken noch mehr funktionale Juwelen, die in der legendären Bibliothek versteckt sind.

Deshalb enthülle ich Ihnen heute ein neues funktionales Gold, das in React vergraben ist, das am besten gehütete Geheimnis von React - Functional setState!

Okay, ich habe mir gerade diesen Namen ausgedacht ... und er ist nicht ganz neu oder ein Geheimnis. Nein, nicht genau. Sehen Sie, es ist ein in React integriertes Muster, das nur wenigen Entwicklern bekannt ist, die sich wirklich tief eingegraben haben. Und es hatte nie einen Namen. Aber jetzt schon - Functional setState!

Nach Dan Abramovs Worten bei der Beschreibung dieses Musters ist Functional setState ein Muster, in dem Sie sich befinden

"Deklarieren Sie Statusänderungen getrennt von den Komponentenklassen."

Huh?

Okay ... was du schon weißt

React ist eine komponentenbasierte UI-Bibliothek. Eine Komponente ist im Grunde eine Funktion, die einige Eigenschaften akzeptiert und ein UI-Element zurückgibt.

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

Eine Komponente muss möglicherweise ihren Status haben und verwalten. In diesem Fall schreiben Sie die Komponente normalerweise als Klasse. Dann haben Sie seinen Status live in der Klassenfunktion constructor:

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

Um den Status zu verwalten, bietet React eine spezielle Methode namens setState(). Sie verwenden es so:

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

Beachten Sie, wie setState()funktioniert. Sie übergeben ihm ein Objekt, das Teile des Status enthält, den Sie aktualisieren möchten. Mit anderen Worten, würde das Objekt , das Sie übergeben hat Schlüssel , um die Schlüssel in dem Komponentenzustand entsprechen, dann setState()aktualisiert oder legt den Zustand , indem das Objekt an den Staat zu verschmelzen. Also "Set-State".

Was du wahrscheinlich nicht wusstest

Erinnerst du dich, wie wir sagten, setState()funktioniert? Was wäre, wenn ich Ihnen sagen würde, dass Sie anstelle eines Objekts eine Funktion übergeben könnten ?

Ja. setState()akzeptiert auch eine Funktion. Die Funktion akzeptiert den vorherigen Status und die aktuellen Requisiten der Komponente, mit denen der nächste Status berechnet und zurückgegeben wird. Siehe unten:

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

Beachten Sie, dass dies setState()eine Funktion ist und wir eine andere Funktion an sie übergeben (funktionale Programmierung… funktionaler setState ). Auf den ersten Blick mag dies hässlich erscheinen, zu viele Schritte, nur um den Status festzulegen. Warum willst du das jemals tun?

Warum eine Funktion an übergeben setState?

Die Sache ist, Statusaktualisierungen können asynchron sein.

Überlegen Sie, was passiert, wenn setState()es aufgerufen wird. React führt zuerst das Objekt, an das Sie übergeben haben, setState()in den aktuellen Status zusammen. Dann wird es diese Versöhnungssache beginnen . Es wird ein neuer React Element-Baum (eine Objektdarstellung Ihrer Benutzeroberfläche) erstellt, der neue Baum gegen den alten Baum unterschieden, anhand des Objekts, an das Sie übergeben haben setState(), herausgefunden, was sich geändert hat , und schließlich das DOM aktualisiert.

Wütend! So viel Arbeit! Dies ist sogar eine stark vereinfachte Zusammenfassung. Aber vertraue auf React!

Reagieren ist nicht einfach "Set-State".

Aufgrund des Arbeitsaufwands wird Ihr Status durch Anrufe setState()möglicherweise nicht sofort aktualisiert.

React kann aus Gründen der Leistung mehrere setState()Anrufe in einem einzigen Update stapeln.

Was bedeutet Reagieren damit?

Erstens können „ mehrere setState()Aufrufe“ bedeuten, dass setState()eine einzelne Funktion mehrmals aufgerufen wird, wie folgt:

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

Wenn React nun auf „ mehrere setState()Anrufe“ stößt , anstatt diesen „Set-State“ dreimal auszuführen, vermeidet React die oben beschriebene enorme Menge an Arbeit und sagt sich klug: „Nein! Ich werde diesen Berg nicht dreimal besteigen und auf jeder einzelnen Reise ein Stück Staat tragen und aktualisieren. Nein, ich möchte lieber einen Container besorgen, all diese Scheiben zusammenpacken und dieses Update nur einmal durchführen. “ Und das, meine FreundeDosierung !

Denken Sie daran, dass das, an das Sie übergeben, setState()ein einfaches Objekt ist. Nehmen wir nun an, dass React immer dann, wenn es auf „ mehrere setState()Aufrufe“ stößt , alle an jeden setState()Aufruf übergebenen Objekte extrahiert , sie zu einem einzigen Objekt zusammenführt und dann dieses einzelne Objekt verwendet setState().

In JavaScript sieht das Zusammenführen von Objekten möglicherweise folgendermaßen aus:

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

Dieses Muster wird als Objektzusammensetzung bezeichnet.

In JavaScript, die Art und Weise „Zusammenführen“ oder Komponieren Objekte Werke ist: Wenn die drei Objekte haben die gleichen Tasten, um den Wert des Schlüssels des letzten Objekts zu übergeben , Object.assign()gewinnt. Zum Beispiel:

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?

Das bedeutet, dass angesichts unserer increaseScoreBy3obigen Funktion das Endergebnis der Funktion nur 1 statt 3 ist, da React den Status nicht sofort in der von uns aufgerufenen Reihenfolge aktualisiert hat setState(). Aber zuerst komponierte React alle Objekte zusammen, was zu folgendem Ergebnis führte: {score : this.state.score + 1}Dann wurde der Status nur einmal gesetzt - mit dem neu komponierten Objekt. So etwas wie das : User.setState({score : this.state.score + 1}.

Um ganz klar zu sein, setState()ist es hier nicht das Problem , ein Objekt an zu übergeben . Das eigentliche Problem besteht darin, ein Objekt an zu übergeben, setState()wenn Sie den nächsten Status aus dem vorherigen Status berechnen möchten. Also hör auf damit. Es ist nicht sicher!

Da this.propsund this.statemöglicherweise asynchron aktualisiert werden, sollten Sie sich bei der Berechnung des nächsten Status nicht auf deren Werte verlassen.

Hier ist ein Stift von Sophia Shoemaker, der dieses Problem demonstriert. Spielen Sie damit und achten Sie auf die schlechten und guten Lösungen in diesem Stift:

FunktionssetState zur Rettung

Wenn Sie keine Zeit mit dem obigen Stift verbracht haben, empfehle ich Ihnen dringend, dies zu tun, da dies Ihnen hilft, das Kernkonzept dieses Beitrags zu verstehen.

Während Sie mit dem Stift oben spielten, haben Sie zweifellos gesehen, dass der funktionale setState unser Problem behoben hat. Aber wie genau?

Lassen Sie uns die Oprah of React - Dan konsultieren.

Beachten Sie die Antwort, die er gab. Wenn Sie funktionale setState machen ...

Aktualisierungen werden in die Warteschlange gestellt und später in der Reihenfolge ausgeführt, in der sie aufgerufen wurden.

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!