Почему вызов метода реагирования setState не приводит к немедленному изменению состояния?

Я читаю Forms раздел и просто попробовал этот код, чтобы продемонстрировать использование onChange (JSBIN).

var React= require('react');

var ControlledForm= React.createClass({
    getInitialState: function() {
        return {
            value: "initial value"
        };
    },

    handleChange: function(event) {
        console.log(this.state.value);
        this.setState({value: event.target.value});
        console.log(this.state.value);

    },

    render: function() {
        return (
            <input type="text" value={this.state.value} onChange={this.handleChange}/>
        );
    }
});

React.render(
    <ControlledForm/>,
  document.getElementById('mount')
);

Когда я обновляю значение <input/> в браузере, второй console.log внутри обратного вызова handleChange печатает тот же value как первый console.log, почему я не вижу результат this.setState({value: event.target.value}) в рамках handleChange обратного вызова?

Ответ 1

Из React документация:

setState() не сразу мутирует this.state, а создает ожидающий переход состояния. Доступ к this.state после вызова этого метод может потенциально вернуть существующее значение. Здесь нет гарантия синхронной работы вызовов на setState и вызовы могут быть собраны для повышения производительности.

Если вы хотите, чтобы функция выполнялась после изменения состояния, передайте ее в качестве обратного вызова.

this.setState({value: event.target.value}, function () {
    console.log(this.state.value);
});

Ответ 2

Как упоминалось в документации React, нет гарантии, что setState будет запущен синхронно, поэтому ваш console.log может вернуть состояние до его обновления.

Майкл Паркер упоминает прохождение обратного вызова в пределах setState. Другим способом обработки логики после изменения состояния является метод componentDidUpdate lifecycle, который является методом, рекомендованным в документах React.

Обычно мы рекомендуем использовать для этой логики componentDidUpdate().

Это особенно полезно, когда может быть последовательный setState запущен, и вы хотели бы запустить ту же функцию после каждого изменения состояния. Вместо добавления обратного вызова для каждого setState, вы можете поместить функцию внутри componentDidUpdate , при необходимости используя определенную логику.

// example
componentDidUpdate(prevProps, prevState) {
  if (this.state.value > prevState.value) {
    this.foo();  
  }
}

Ответ 3

Вы можете попробовать использовать ES7 async/await. Например, используя ваш пример:

handleChange: async function(event) {
    console.log(this.state.value);
    await this.setState({value: event.target.value});
    console.log(this.state.value);
}

Ответ 5

Здесь React docs говорят:

НИКОГДА не мутируйте this.state напрямую, так как вызов setState() впоследствии может заменить  мутацию, которую вы сделали. Относитесь к this.state как к неизменному.

setState() не сразу мутирует this.state, но создает состояние ожидания  переход. Доступ к this.state после вызова этого метода может потенциально  вернуть существующее значение.

Нет гарантии синхронной работы вызовов setState и вызовов  могут быть собраны для повышения производительности. setState() всегда вызывает повторную  рендеринг, если логика условного воспроизведения не реализована в  shouldComponentUpdate().

Если используются изменяемые объекты, и логика не может быть реализована в  shouldComponentUpdate(), вызывающий setState() только тогда, когда новое состояние отличается  из предыдущего состояния избежит ненужных повторных рендерингов.

Ответ 6

Ну, так глупые soloution, но, возможно, это может помочь кому-то, кто не знаком с резервами. В моем случае я решил проблему, используя временную переменную.

У меня есть тест для пользователя, и я вычисляю количество правильных ответов на вопросы, сохраняя их до состояния теста, и после ответа на последний вопрос мне сразу нужно отобразить количество правильных ответов от состояния. Однако государство возвращало старую ценность. Поэтому я сделал это:

const numberOfCorrectQuestionsTemp = this.state.numberOfCorrectQuestions;
if (questionCorrect) {
    numberOfCorrectQuestionsTemp++;
}
this.setState({
    numberOfCorrectQuestions: numberOfCorrectQuestionsTemp,
});
if (isLastQuestion) {
    // display the result in percentage - with actual new value
    alert((100 / (this.state.numberOfQuestions)) * numberOfCorrectQuestionsTemp + '%');
}

Но лучшим решением сделать что-то после асинхронного действия является использование обратного вызова. Вы можете найти его здесь: Когда использовать обратный вызов React setState

Ответ 7

Синтаксис async-await отлично работает для чего-то вроде следующего...

changeStateFunction = () => {
  // Some Worker..

  this.setState((prevState) => ({
  year: funcHandleYear(),
  month: funcHandleMonth()
}));

goNextMonth = async () => {
  await this.changeStateFunction();
  const history = createBrowserHistory();
  history.push('/calendar?year=${this.state.year}&month=${this.state.month}');
}

goPrevMonth = async () => {
  await this.changeStateFunction();
  const history = createBrowserHistory();
  history.push('/calendar?year=${this.state.year}&month=${this.state.month}');
}

Ответ 8

Проще говоря - this.setState({data: value}) является асинхронным по своей природе, что означает, что он выходит из стека вызовов и возвращается только в стек вызовов, если он не разрешен.

Пожалуйста, прочитайте о цикле событий, чтобы иметь четкое представление об асинхронной природе в JS и почему требуется время для обновления -

https://medium.com/front-end-weekly/javascript-event-loop-explained-4cd26af121d4

Отсюда -

    this.setState({data:value});
    console.log(this.state.data); // will give undefined or unupdated value

как требуется время для обновления. Для достижения вышеуказанного процесса -

    this.setState({data:value},function () {
     console.log(this.state.data);
    });