Kann ich eine Funktion ausführen, nachdem die Aktualisierung von setState abgeschlossen ist?

174

Ich bin sehr neu in React JS (wie in, gerade erst heute begonnen). Ich verstehe nicht ganz, wie setState funktioniert. Ich kombiniere React und Easel JS, um ein Raster basierend auf Benutzereingaben zu zeichnen. Hier ist mein JS-Bin: http://jsbin.com/zatula/edit?js,output

Hier ist der Code:

        var stage;

    var Grid = React.createClass({
        getInitialState: function() {
            return {
                rows: 10,
                cols: 10
            }
        },
        componentDidMount: function () {
            this.drawGrid();
        },
        drawGrid: function() {
            stage = new createjs.Stage("canvas");
            var rectangles = [];
            var rectangle;
            //Rows
            for (var x = 0; x < this.state.rows; x++)
            {
                // Columns
                for (var y = 0; y < this.state.cols; y++)
                {
                    var color = "Green";
                    rectangle = new createjs.Shape();
                    rectangle.graphics.beginFill(color);
                    rectangle.graphics.drawRect(0, 0, 32, 44);
                    rectangle.x = x * 33;
                    rectangle.y = y * 45;

                    stage.addChild(rectangle);

                    var id = rectangle.x + "_" + rectangle.y;
                    rectangles[id] = rectangle;
                }
            }
            stage.update();
        },
        updateNumRows: function(event) {
            this.setState({ rows: event.target.value });
            this.drawGrid();
        },
        updateNumCols: function(event) {
            this.setState({ cols: event.target.value });
            this.drawGrid();
        },
        render: function() {
            return (
                <div>
                    <div className="canvas-wrapper">
                        <canvas id="canvas" width="400" height="500"></canvas>
                        <p>Rows: { this.state.rows }</p>
                        <p>Columns: {this.state.cols }</p>
                    </div>
                    <div className="array-form">
                        <form>
                            <label>Number of Rows</label>
                            <select id="numRows" value={this.state.rows} onChange={ this.updateNumRows }>
                                <option value="1">1</option>
                                <option value="2">2</option>
                                <option value ="5">5</option>
                                <option value="10">10</option>
                                <option value="12">12</option>
                                <option value="15">15</option>
                                <option value="20">20</option>
                            </select>
                            <label>Number of Columns</label>
                            <select id="numCols" value={this.state.cols} onChange={ this.updateNumCols }>
                                <option value="1">1</option>
                                <option value="2">2</option>
                                <option value="5">5</option>
                                <option value="10">10</option>
                                <option value="12">12</option>
                                <option value="15">15</option>
                                <option value="20">20</option>
                            </select>
                        </form>
                    </div>    
                </div>
            );
        }
    });
    ReactDOM.render(
        <Grid />,
        document.getElementById("container")
    );

Sie können im JS-Bin sehen, dass beim ersten Ändern der Anzahl der Zeilen oder Spalten mit einem der Dropdowns nichts passiert. Wenn Sie das nächste Mal einen Dropdown-Wert ändern, zeichnet das Raster die Zeilen- und Spaltenwerte des vorherigen Status. Ich vermute, dass dies geschieht, weil meine Funktion this.drawGrid () ausgeführt wird, bevor setState abgeschlossen ist. Vielleicht gibt es noch einen anderen Grund?

Vielen Dank für Ihre Zeit und Hilfe!

monalisa717
quelle

Antworten:

448

setState(updater[, callback]) ist eine asynchrone Funktion:

https://facebook.github.io/react/docs/react-component.html#setstate

Sie können eine Funktion ausführen, nachdem setState beendet wurde, indem Sie den zweiten Parameter callbackwie folgt verwenden :

this.setState({
    someState: obj
}, () => {
    this.afterSetStateFinished();
});
sytolk
quelle
32
oder einfach this.setState ({someState: obj}, this.afterSetStateFinished);
Aleksei Prokopov
Ich mag hinzufügen , dass in meinem speziellen Beispiel, ich wahrscheinlich nennen will this.drawGrid()in componentDidUpdatewie @kennedyyu vorgeschlagen, weil zu jeder Zeit setStatebeendet ist, werde ich das Gitter neu gezeichnet werden soll (so würde dies einige Zeilen Code speichern), aber erreichen die gleiche Sache .
Monalisa717
Tolle Antwort ... hat mir sehr geholfen
MoHammaD ReZa DehGhani
Das spart mir viel Zeit. Vielen Dank.
Nayan Dey
28

renderwird jedes Mal aufgerufen, wenn Sie setStatedie Komponente bei Änderungen erneut rendern. Wenn Sie Ihren Aufruf drawGriddorthin verschieben, anstatt ihn in Ihren update*Methoden aufzurufen , sollten Sie kein Problem haben.

Wenn das bei Ihnen nicht funktioniert, gibt es auch eine Überlastung setState, bei der ein Rückruf als zweiter Parameter verwendet wird. Sie sollten dies als letzten Ausweg nutzen können.

Justin Niessner
quelle
2
Vielen Dank - Ihr erster Vorschlag funktioniert und (meistens) sinnvoll. Ich habe dies getan: render: function() { this.drawGrid(); return......
monalisa717
3
Ähm, bitte tu das nicht in render()... sytolks Antwort sollte die akzeptierte sein
mfeineis
Dies ist eine falsche Antwort. SetState wechselt zur Infintie-Schleife und stürzt die Seite ab.
Maverick
Justin, ich bin daran interessiert, warum der Rückruf von setStateals letztes Mittel verwendet werden sollte. Ich stimme den meisten Leuten hier zu, dass es sinnvoll ist, diesen Ansatz zu verwenden.
monalisa717
1
@Rohmer Dies ist viel zu teuer für die Ausführung bei jedem Renderaufruf, während es offensichtlich auch nicht bei jedem Aufruf benötigt wird. Wenn es rein reactwäre, würde das vdom in den meisten Fällen dafür sorgen, dass nicht zu viel Arbeit geleistet wird. Dies ist eine Interop mit einer anderen Bibliothek, die Sie minimieren möchten
mfeineis
14

Etwas herstellen setStateRückkehr einPromise

Zusätzlich zur Übergabe einer callbackto- setState()Methode können Sie sie um eine asyncFunktion wickeln und die then()Methode verwenden. In einigen Fällen kann dies zu einem saubereren Code führen:

(async () => new Promise(resolve => this.setState({dummy: true}), resolve)()
    .then(() => { console.log('state:', this.state) });

Und hier können Sie noch einen Schritt weiter gehen und eine wiederverwendbare setStateFunktion erstellen , die meiner Meinung nach besser ist als die obige Version:

const promiseState = async state =>
    new Promise(resolve => this.setState(state, resolve));

promiseState({...})
    .then(() => promiseState({...})
    .then(() => {
        ...  // other code
        return promiseState({...});
    })
    .then(() => {...});

Dies funktioniert gut in React 16.4, aber ich habe es in früheren Versionen von React noch nicht getestet .

Erwähnenswert ist auch, dass es in den meisten - wahrscheinlich allen Fällen - besser ist , Ihren Rückrufcode in der componentDidUpdateMethode zu belassen.

Mahdi
quelle
11

Wenn neue Requisiten oder Zustände empfangen werden (wie Sie setStatehier anrufen ), ruft React einige Funktionen auf, die aufgerufen werden componentWillUpdateundcomponentDidUpdate

Fügen Sie in Ihrem Fall einfach eine componentDidUpdateaufzurufende Funktion hinzuthis.drawGrid()

Hier ist Arbeitscode in JS Bin

Wie bereits erwähnt, wird im Code danach componentDidUpdateaufgerufenthis.setState(...)

dann wird componentDidUpdatedrinnen anrufenthis.drawGrid()

Weitere Informationen zum Komponentenlebenszyklus finden Sie unter Reagieren unter https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate

Kennedy Yu
quelle
anstatt nur den Link einzufügen. Bitte fügen Sie die entsprechenden Teile in die Antwort ein.
Phoenix