Могу ли я выполнить функцию после завершения обновления setState?

175

Я очень новичок в React JS (как, только начал сегодня). Я не совсем понимаю, как работает setState. Я комбинирую React и Easel JS, чтобы нарисовать сетку на основе пользовательского ввода. Вот моя корзина JS: http://jsbin.com/zatula/edit?js,output

Вот код:

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

Вы можете видеть в JS bin, когда вы изменяете количество строк или столбцов одним из выпадающих меню, ничего не произойдет в первый раз. В следующий раз, когда вы измените значение раскрывающегося списка, сетка отобразит значения строки и столбца предыдущего состояния. Я предполагаю, что это происходит, потому что моя функция this.drawGrid () выполняется до завершения setState. Может быть, есть другая причина?

Спасибо за ваше время и помощь!

monalisa717
источник

Ответы:

450

setState(updater[, callback]) это асинхронная функция:

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

Вы можете выполнить функцию после завершения setState, используя второй параметр, callbackнапример:

this.setState({
    someState: obj
}, () => {
    this.afterSetStateFinished();
});
sytolk
источник
33
или просто this.setState ({someState: obj}, this.afterSetStateFinished);
Алексей Прокопов
Я хочу добавить , что в моем конкретном примере, я , вероятно , хотите , чтобы позвонить this.drawGrid()в componentDidUpdateкак @kennedyyu предложил, потому что какое -то время setStateзакончится, я хочу , чтобы перерисовать сетку (так что это позволит сэкономить несколько строк кода), но сделать то же самое ,
monalisa717
Отличный ответ ... мне очень помогли
MoHammaD ReZa DehGhani
Это экономит много времени для меня. Спасибо.
Наян Дей
28

renderбудет вызываться каждый раз при setStateповторном рендеринге компонента, если есть изменения. Если вы переместили свой вызов drawGridтуда, а не вызывали его в своих update*методах, у вас не должно возникнуть проблем.

Если это не работает для вас, есть также перегрузка, setStateкоторая принимает обратный вызов в качестве второго параметра. Вы должны быть в состоянии использовать это в качестве крайней меры.

Джастин Нисснер
источник
2
Спасибо - ваше первое предложение работает и (в основном) имеет смысл. Я сделал это: render: function() { this.drawGrid(); return......
monalisa717
3
Гм, пожалуйста, не делайте этого в render()... ответ Ситолка должен быть принят
mfeineis
Это неправильный ответ, setState переходит в бесконечный цикл и вылетает на страницу.
Maverick
Джастин, меня интересует, почему ответный вызов setStateдолжен использоваться в крайнем случае. Я согласен с большинством здесь присутствующих, что имеет смысл использовать этот подход.
monalisa717
1
@Rohmer это дорогостоящий способ выполнения при каждом вызове рендеринга, хотя, очевидно, он не нужен и при каждом вызове. Если бы это было чисто, reactvdom в большинстве случаев позаботился бы о том, чтобы не делать слишком много работы, это взаимодействие с другой библиотекой, которую вы хотите минимизировать
mfeineis
14

Делая setStateвозвратPromise

В дополнение к передаче метода callbackto setState()вы можете обернуть его вокруг asyncфункции и использовать then()метод, который в некоторых случаях может привести к созданию более чистого кода:

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

И здесь вы можете сделать еще один шаг вперед и создать функцию многократного использования, setStateкоторая, на мой взгляд, лучше, чем в приведенной выше версии:

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

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

Это хорошо работает в React 16.4, но я еще не тестировал его в более ранних версиях React .

Также стоит упомянуть, что хранение кода обратного вызова в componentDidUpdateметоде - это лучшая практика в большинстве - возможно, во всех случаях.

мессия
источник
11

когда поступают новые реквизиты или состояния (как вы называете setStateздесь), React будет вызывать некоторые функции, которые вызываются componentWillUpdateиcomponentDidUpdate

в вашем случае просто добавьте componentDidUpdateфункцию для вызоваthis.drawGrid()

вот рабочий код в JS Bin

как я уже упоминал, в коде componentDidUpdateбудет вызываться послеthis.setState(...)

тогда componentDidUpdateвнутри будет звонитьthis.drawGrid()

Узнайте больше о жизненном цикле компонента в React https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate

Кеннеди Ю
источник
вместо того, чтобы просто вставить ссылку. пожалуйста, добавьте соответствующие части в ответе.
Феникс