Posso eseguire una funzione al termine dell'aggiornamento di setState?


175

Sono molto nuovo su React JS (come in, appena iniziato oggi). Non capisco bene come funzioni setState. Sto combinando React e Easel JS per disegnare una griglia basata sull'input dell'utente. Ecco il mio cestino JS: http://jsbin.com/zatula/edit?js,output

Ecco il codice:

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

Puoi vedere nel cestino JS quando cambi il numero di righe o colonne con uno dei menu a discesa, non accadrà nulla la prima volta. Alla successiva modifica di un valore a discesa, la griglia attirerà i valori di riga e colonna dello stato precedente. Immagino che ciò stia accadendo perché la mia funzione this.drawGrid () viene eseguita prima che setState sia completo. Forse c'è un altro motivo?

Grazie per il tuo tempo e aiuto!

Risposte:


450

setState(updater[, callback]) è una funzione asincrona:

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

È possibile eseguire una funzione dopo che setState ha terminato utilizzando il secondo parametro callbackcome:

this.setState({
    someState: obj
}, () => {
    this.afterSetStateFinished();
});

33
o solo this.setState ({someState: obj}, this.afterSetStateFinished);
Aleksei Prokopov,

Voglio aggiungere che nel mio caso specifico, io probabilmente voglio chiamare this.drawGrid()in componentDidUpdatecome @kennedyyu suggerito, perché ogni volta che setStateè finito, vorrò ridisegnare la griglia (quindi questo permetterebbe di risparmiare alcune righe di codice), ma ottenere la stessa cosa .
monalisa717

Ottima risposta ... mi ha aiutato molto
MoHammaD ReZa DehGhani,

Mi fa risparmiare molto tempo. Grazie.
Nayan Dey,

28

renderverrà chiamato ogni volta che si esegue setStateil rendering del componente in caso di modifiche. Se trasferisci la tua chiamata drawGridlì anziché chiamarla nei tuoi update*metodi, non dovresti avere problemi.

Se questo non funziona per te, c'è anche un sovraccarico setStateche accetta una richiamata come secondo parametro. Dovresti essere in grado di trarne vantaggio come ultima risorsa.


2
Grazie - il tuo primo suggerimento funziona e (principalmente) ha senso. Ho fatto questo: render: function() { this.drawGrid(); return......
monalisa717

3
Ahem, per favore, non farlo in render()... la risposta di Sytolk dovrebbe essere quella accettata
mfeineis,

Questa è una risposta sbagliata, setState andrà per il ciclo di infintie e arresterà la pagina.
Maverick,

Justin, sono interessato al motivo per cui il callback di setStatedovrebbe essere usato come ultima risorsa. Sono d'accordo con la maggior parte delle persone qui sul fatto che abbia senso usare questo approccio.
monalisa717

1
@Rohmer questo è un modo costoso da eseguire su ogni chiamata di rendering mentre ovviamente non è necessario su ogni chiamata. Se fosse puro reactil vdom si preoccuperebbe di non fare troppo lavoro nella maggior parte dei casi, si tratta di interoperare con un'altra libreria che vuoi minimizzare
mfeineis

14

Fare setStateritorno aPromise

Oltre a passare un metodo callbackto setState(), puoi racchiuderlo in una asyncfunzione e utilizzare il then()metodo, che in alcuni casi potrebbe produrre un codice più pulito:

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

E qui puoi fare un ulteriore passo avanti e creare una setStatefunzione riutilizzabile che a mio avviso è migliore della versione precedente:

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

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

Funziona bene in React 16.4, ma non l'ho ancora testato nelle versioni precedenti di React .

Vale anche la pena ricordare che mantenere il codice di callback nel componentDidUpdatemetodo è una pratica migliore nella maggior parte - probabilmente in tutti i casi.


11

quando vengono ricevuti nuovi oggetti di scena o stati (come si chiama setStatequi), React invocherà alcune funzioni, che sono chiamate componentWillUpdateecomponentDidUpdate

nel tuo caso, aggiungi semplicemente una componentDidUpdatefunzione da chiamarethis.drawGrid()

qui è il codice di lavoro in JS Bin

come ho già detto, nel codice, componentDidUpdateverrà invocato dopothis.setState(...)

allora componentDidUpdatedentro sta per chiamarethis.drawGrid()

leggi di più sul ciclo di vita dei componenti in React https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate


invece di incollare il link. si prega di aggiungere le parti pertinenti nella risposta.
fenice
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.