【问题标题】:Why calling react setState method doesn't mutate the state immediately?为什么调用 react setState 方法不会立即改变状态?
【发布时间】:2015-08-27 06:37:23
【问题描述】:

我正在阅读 文档的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')
);

当我在浏览器中更新&lt;input/&gt; 值时,handleChange 回调中的第二个console.log 打印与第一个value 相同的console.log,为什么我看不到@987654332 的结果@在handleChange回调范围内?

【问题讨论】:

标签: reactjs javascript reactjs react-jsx


【解决方案1】:

来自 React 的 documentation

setState() 不会立即改变 this.state 而是创建一个 等待状态转换。调用后访问this.state 方法可能会返回现有值。没有 保证对setState 的调用同步操作,并且调用可能 进行批处理以获得性能提升。

如果您希望在状态更改发生后执行函数,请将其作为回调传递。

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

【讨论】:

  • 好答案。我需要做的观察是小心使用 valueLink。如果您不必格式化/屏蔽输入,则效果很好。
  • 您可能还想查看componentDidUpdate。状态改变后调用。
  • 快速提问,如果可以的话,我看到一旦我们将我们需要的函数作为回调传递给 setState ,我希望在调用 render() 之前先执行该函数。但我看到顺序是 setState() -> render() -> setStates' callback() 。这是正常的吗?如果我们想根据我们在回调中所做的事情来控制我们的渲染怎么办? 应该组件更新
  • 改变组件的状态总是会触发重新渲染,除非shouldComponentUpdate 中有另外指定的行为。在您传递给setState 的回调中,您到底想在重新渲染之前执行什么操作?
  • ...为什么?有人可以证明这一点吗?
【解决方案2】:

如 React 文档中所述,不能保证 setState 被同步触发,因此您的 console.log 可能会在更新之前返回状态。

Michael Parker 提到在 setState 中传递回调。状态改变后处理逻辑的另一种方法是通过 componentDidUpdate 生命周期方法,这是 React 文档中推荐的方法。

通常我们建议使用 componentDidUpdate() 来代替此类逻辑。

这在可能有连续的setStates 被触发时特别有用,并且您希望在每次状态更改后触发相同的函数。您可以将函数放置在 componentDidUpdate 中,而不是为每个 setState 添加回调,必要时在其中添加特定逻辑。

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

    【讨论】:

    • 您的答案与其他高质量答案有何不同?
    • 另一个答案是关于在 setState() 中使用回调。我想我把它放在这里是为了那些不适用回调用例的人。例如,当我自己遇到这个问题时,我的用例在设置后立即涉及更新状态的开关案例。因此使用 async/await 优于使用回调。
    • 如果我总是在想要更新某些状态时总是使用 await 然后等待它更新,这会影响性能吗?如果我将多个等待 setStates 放在一个链下,它会在每次 setState 更新后呈现吗?还是在上次 setState 更新之后?
    【解决方案4】:

    注意 react 生命周期方法!

    我工作了几个小时才发现getDerivedStateFromProps 将在每个setState() 之后被调用。

    ?

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        简单地说 - 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);
            });
        

        【讨论】:

          【解决方案7】:

          由于setState的异步特性,调用setState方法后访问this.state不能保证返回更新状态。

          为保证在致电setState 后有更新,您可以采用两种解决方案。

          解决方案 1: 作为上述答案之一的mentioned,将您的代码放入componentDidUpdate 方法中

          解决方案 2:如上述答案的another 中所述,将您的东西作为回调传递

           this.setState({value: myValue}, function () {
              this.functionThatIsExecutedWhenStateIsUpdated();
          });
          

          请务必注意,这两种解决方案显然不能互换。一个不能轻易解决另一个的所有用例。作为一般规则,如果可以,最佳实践表明解决方案 1 是首选。但是,在某些用例中,只有解决方案 2“更有效”有效,例如“update-my-view-and-post-my-data”用例。这个用例是这样的:

          添加一个项目后,比如“添加计划”,我想both将该项目添加到前端列表并立即将刚刚更新的列表发布到后端,如图所示在下面的概念中:

          如果您不采取任何一种解决方案,即如果您只在代码中这样说:

          addToItemArray = () => { 
               this.setState{{ scheduledItemsArray: newObjectListWithMax}}   
               this.postData();
          }
          
          <button className="btn btn-secondary btn-block" onClick={this.addToItemArray}>Add Shedule</button>
          

          ...您将发布列表排除“Delivery to Max”项目,因为当您this.postData() 时状态不会更新(同样,因为它是异步的)。

          如果您使用解决方案 1,您将在 Schedule Name 文本框中输入每个字符后进行 POST!

          还有其他方法可以满足此用例,但解决方案 2 在阅读代码时最能传达意图。

          鉴于此用例在几乎每个 Web 应用程序中无处不在,Michael's answer 解释的回调技术是每个开发人员工具包中不可或缺的一段代码。

          【讨论】:

            猜你喜欢
            • 2018-08-17
            • 1970-01-01
            • 1970-01-01
            • 2017-03-30
            相关资源
            最近更新 更多