Чому виклик методу реакції setState не змінює стан одразу?


326

Я читаю розділ " Форми "документація та щойно спробував цей код для демонстрації 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зворотного дзвінка?


Якщо ви використовуєте гачки, погляньте на метод useState, який не відображає зміни відразу .
Еміль Бержерон

Відповіді:


615

З документації React :

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

Якщо ви хочете, щоб функція виконувалася після зміни стану, передайте її як зворотний виклик.

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

Хороша відповідь. Спостереження, що мені потрібно зробити, - це бути обережним, щоб використовувати valueLink. Це добре працює, якщо вам не доведеться форматувати / маскувати введення.
Дерік

42
Ви також можете перевірити componentDidUpdate. Він буде називатися після зміни штату.
Keysox

1
Швидке запитання, якщо я можу, я бачу, як тільки ми передамо функцію, яка нам потрібна як зворотний виклик для setState, я сподівався, що функція буде виконана спочатку до виклику render (). Але я бачу, що порядок встановленийState () -> render () -> зворотний виклик setStates (). це нормально? Що робити, якщо ми хочемо контролювати наш рендер на основі речей, які ми робимо в зворотному дзвінку? shouldComponentUpdate ?
semuzaboi

2
Зміна стану компонента завжди призведе до повторного відображення, якщо немає поведінки, shouldComponentUpdateяка не визначає інше. Що саме ви намагаєтеся зробити у зворотному дзвінку, який ви переходите до того, setStateщо ви хочете здійснити до повторного відтворення?
Майкл Паркер

4
... чому? Чи може хтось це виправдати?
JackHasaKeyboard

49

Як зазначено в документації React, немає гарантії, що setStateвона буде синхронізована, тому ви console.logможете повернути стан перед оновленням.

Майкл Паркер згадує про зворотний виклик у межах setState. Інший спосіб управління логікою після зміни стану - це componentDidUpdateметод життєвого циклу, який є методом, рекомендованим у документах React.

Як правило, ми рекомендуємо замість цього використовувати компонентDidUpdate () для такої логіки.

Це особливо корисно, коли можуть бути послідовні setStateзвільнення, і ви хочете запускати ту саму функцію після кожної зміни стану. Замість того, щоб додавати зворотний дзвінок до кожного setState, ви можете розмістити функцію всередині componentDidUpdate, із певною логікою всередині.

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

16

Ви можете спробувати використовувати ES7 async / await. Наприклад, використовуючи ваш приклад:

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

Чим ваша відповідь відрізняється від іншої високоякісної відповіді?
Тод

9
Інша відповідь стосується використання зворотного дзвінка в setState (). Я думав, що я ставлю це тут для тих, до кого не застосовується випадок використання зворотного дзвінка. Наприклад, коли я сам стикався з цією проблемою, мій випадок використання включав випадок переключення на оновлений стан відразу після його встановлення. Тому використання async / await було віддано перевагу використанню зворотного дзвінка.
kurokiiru

чи вплине це на ефективність, якщо я завжди використовую функцію очікування завжди, коли хочу оновити деякий стан, а потім чекати, коли оновлення оновиться? І якщо я покладу декілька очікуючих setStates у ланцюжку один під іншим, чи буде він відображатися після кожного оновлення setState? або після останнього оновлення setState?
користувач2774480


Що стосується того, що ви запитуєте user2774480, я вважаю, що все зводиться до конкретного випадку використання, щоб визначити, яку реалізацію використовувати. Якщо в ланцюжку використовуються кілька setStates, продуктивність буде впливати, і так, вона відображатиметься після кожного setState, але виправте мене, якщо я помиляюся.
kurokiiru


-1

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}`);
}

-1

Простіше кажучи - 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);
    });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.