Czy mogę wykonać funkcję po zakończeniu aktualizacji setState?

174

Jestem bardzo nowy w React JS (tak jak w, dopiero dzisiaj zacząłem). Nie do końca rozumiem, jak działa setState. Łączę React i Easel JS, aby narysować siatkę na podstawie danych wejściowych użytkownika. Oto mój bin JS: http://jsbin.com/zatula/edit?js,output

Oto kod:

        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")
    );

Możesz zobaczyć w koszu JS, gdy zmienisz liczbę wierszy lub kolumn za pomocą jednego z menu rozwijanych, nic się nie stanie za pierwszym razem. Następnym razem, gdy zmienisz wartość listy rozwijanej, siatka zostanie narysowana do wartości wierszy i kolumn poprzedniego stanu. Domyślam się, że to się dzieje, ponieważ moja funkcja this.drawGrid () jest wykonywana przed zakończeniem setState. Może jest inny powód?

Dziękuję za poświęcony czas i pomoc!

monalisa717
źródło

Odpowiedzi:

449

setState(updater[, callback]) jest funkcją asynchroniczną:

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

Możesz wykonać funkcję po zakończeniu setState, używając drugiego parametru, callbacktakiego jak:

this.setState({
    someState: obj
}, () => {
    this.afterSetStateFinished();
});
sytolk
źródło
32
lub po prostu this.setState ({someState: obj}, this.afterSetStateFinished);
Aleksei Prokopov
Chcę dodać, że w moim przykładzie konkretnego, pewnie chcesz się połączyć this.drawGrid()w componentDidUpdatejak @kennedyyu zasugerował, ponieważ za każdym razem setStatejest zakończone, będę chciał przerysować siatkę (tak byłoby to zaoszczędzić kilka linii kodu), ale osiągnąć to samo .
monalisa717
Świetna odpowiedź ... bardzo mi pomogła
MohammaD ReZa DehGhani
Oszczędza mi to dużo czasu. Dzięki.
Nayan Dey
28

renderzostanie wywołana za każdym razem, gdy będziesz setStateponownie renderować komponent, jeśli nastąpią zmiany. Jeśli przenosisz tam swoje wywołanie drawGridzamiast wywoływać je w swoich update*metodach, nie powinieneś mieć problemu.

Jeśli to nie zadziała, istnieje również przeciążenie, setStatektóre pobiera wywołanie zwrotne jako drugi parametr. Powinieneś mieć możliwość skorzystania z tego w ostateczności.

Justin Niessner
źródło
2
Dziękuję - Twoja pierwsza sugestia działa i (głównie) ma sens. Zrobiłem to: render: function() { this.drawGrid(); return......
monalisa717
3
Ehem, proszę nie rób tego w render()... odpowiedź sytolk powinna być akceptowana
mfeineis
To jest zła odpowiedź, setState przejdzie do pętli infintie i spowoduje awarię strony.
Maverick
Justin, interesuje mnie, dlaczego powiedziałem, że wywołanie zwrotne setStatepowinno być używane w ostateczności. Zgadzam się z większością obecnych tutaj ludzi, że warto zastosować takie podejście.
monalisa717
1
@Rohmer jest to zbyt kosztowne do wykonania przy każdym wywołaniu renderowania, podczas gdy oczywiście nie jest też potrzebne przy każdym wywołaniu. Jeśli to była czysta reactvdom by dbać o nie robi zbyt wiele pracy w większości przypadków jest to współdziałanie z inną biblioteką chcesz zminimalizować
mfeineis
14

Dokonywanie setStatezwrotu aPromise

Oprócz przekazania metody callbackdo setState(), możesz owinąć ją wokół asyncfunkcji i użyć then()metody - co w niektórych przypadkach może dać bardziej przejrzysty kod:

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

I tutaj możesz zrobić kolejny krok do przodu i zrobić funkcję wielokrotnego użytku, setStatektóra moim zdaniem jest lepsza niż powyższa wersja:

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

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

Działa to dobrze w React 16.4, ale nie testowałem tego we wcześniejszych wersjach Reacta .

Warto również wspomnieć, że przechowywanie kodu wywołania zwrotnego wcomponentDidUpdate metodzie jest lepszą praktyką w większości - prawdopodobnie we wszystkich przypadkach.

Mahdi
źródło
11

gdy otrzymywane są nowe właściwości lub stany (jak to nazywasz setStatetutaj), React wywoła pewne funkcje, które są wywoływane componentWillUpdateicomponentDidUpdate

w twoim przypadku po prostu dodaj componentDidUpdatefunkcję do wywołaniathis.drawGrid()

tutaj działa kod w JS Bin

jak wspomniałem w kodzie, componentDidUpdatezostanie wywołany późniejthis.setState(...)

wtedy w componentDidUpdateśrodku zadzwonithis.drawGrid()

przeczytaj więcej o cyklu życia komponentów w React https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate

Kennedy Yu
źródło
zamiast po prostu wklejać link. proszę dodać odpowiednie fragmenty odpowiedzi.
phoenix