React是否保留状态更新的顺序?

时间:2018-02-01 13:13:37

标签: javascript reactjs state setstate

我知道React可以异步和批量执行状态更新以进行性能优化。因此,在调用setState后,您永远不能相信要更新的状态。但是你能相信React能够setState被称为的相同顺序更新状态

  1. 相同的组件?
  2. 不同的组件?
  3. 考虑点击以下示例中的按钮:

    1。 a是否为假,b为真

    class Container extends React.Component {
      constructor(props) {
        super(props);
        this.state = { a: false, b: false };
      }
    
      render() {
        return <Button onClick={this.handleClick}/>
      }
    
      handleClick = () => {
        this.setState({ a: true });
        this.setState({ b: true });
      }
    }
    

    2。 a是否有可能是假的,b是真的

    class SuperContainer extends React.Component {
      constructor(props) {
        super(props);
        this.state = { a: false };
      }
    
      render() {
        return <Container setParentState={this.setState.bind(this)}/>
      }
    }
    
    class Container extends React.Component {
      constructor(props) {
        super(props);
        this.state = { b: false };
      }
    
      render() {
        return <Button onClick={this.handleClick}/>
      }
    
      handleClick = () => {
        this.props.setParentState({ a: true });
        this.setState({ b: true });
      }
    }
    

    请记住,这些是我的用例的极端简化。我意识到我可以这样做,例如在示例1中同时更新两个状态参数,以及在示例2中的第一个状态更新的回调中执行第二个状态更新。但是,这不是我的问题,我只关心是否存在React执行这些状态更新的明确方式,没有别的。

    非常感谢文档支持的任何答案。

4 个答案:

答案 0 :(得分:253)

我在React工作。

<强> TLDR:

  

但是你能相信React以与调用setState相同的顺序更新状态吗

     
      
  • 相同的组件?
  •   

  
      
  • 不同的组件?
  •   

始终遵守更新的顺序。你是否看到了一个中间状态&#34;之间的#34;他们与否取决于你是否批量进入。

目前(React 16及更早版本),默认情况下仅对React事件处理程序内的更新进行批处理。在您需要时,有一个不稳定的API可以强制在事件处理程序之外进行批处理。

在未来版本(可能是React 17及更高版本)中,React默认会批量处理所有更新,因此您不必考虑这一点。与往常一样,我们会在React blog和发行说明中公布对此的任何更改。

理解这一点的关键是无论你在React事件处理程序中执行了多少个{em>>调用,它们只产生一个re-在活动结束时呈现。这对于大型应用程序的良好性能至关重要,因为如果setState()Child在处理点击事件时每次调用Parent,则您不想重新呈现{{1}两次。

在两个示例中,setState()调用都发生在React事件处理程序中。因此,它们总是在事件结束时被冲洗在一起(你不会看到中间状态)。

更新始终按其出现的顺序浅层合并。因此,如果第一次更新为Child,第二次更新为setState(),第三次更新为{a: 10},则呈现状态为{b: 20}。对同一状态键的更新更新(例如,在我的示例中类似{a: 30})总是&#34;赢得&#34;。

当我们在批处理结束时重新呈现UI时,{a: 30, b: 20}对象会更新。因此,如果您需要根据以前的状态更新状态(例如递增计数器),则应使用功能a版本来提供先前的状态,而不是从this.state读取。如果您对此的理由感到好奇,我会深入解释in this comment

在你的例子中,我们不会看到&#34;中间状态&#34;因为我们在React事件处理程序启用了批处理(因为React&#34;知道&#34;当我们退出该事件时)。

但是,在React 16和早期版本中,默认情况下,在React事件处理程序之外没有默认批处理。因此,如果在您的示例中我们有一个AJAX响应处理程序而不是setState(fn),则每个this.state都会在发生时立即处理。在这种情况下,是的,您看到中间状态:

handleClick

我们发现行为不同取决于您是否处于事件处理程序中,这是不方便的。这将在未来的React版本中更改,默认情况下将批量处理所有更新(并提供一个选择性API以同步刷新更改)。在我们切换默认行为之前(可能在React 17中),有一个可用于强制批处理的API

setState()

内部React事件处理程序都包含在promise.then(() => { // We're not in an event handler, so these are flushed separately. this.setState({a: true}); // Re-renders with {a: true, b: false } this.setState({b: true}); // Re-renders with {a: true, b: true } this.props.setParentState(); // Re-renders the parent }); 中,这就是默认情况下它们被批量处理的原因。请注意,在promise.then(() => { // Forces batching ReactDOM.unstable_batchedUpdates(() => { this.setState({a: true}); // Doesn't re-render yet this.setState({b: true}); // Doesn't re-render yet this.props.setParentState(); // Doesn't re-render yet }); // When we exit unstable_batchedUpdates, re-renders once }); 中包装更新两次无效。当我们退出最外面的unstable_batchedUpdates呼叫时,刷新更新。

该API不稳定&#34;在默认情况下已经启用批处理时我们将删除它。但是,我们不会在次要版本中删除它,所以如果你需要在React事件处理程序之外的某些情况下强制批处理,你可以安全地依赖它直到React 17。

总而言之,这是一个令人困惑的主题,因为React默认只在事件处理程序中批处理。这将在未来版本中发生变化,然后行为将变得更加直截了当。但解决方案不是批量减少,默认情况下批量更多。这就是我们要做的事情。

答案 1 :(得分:3)

这实际上是一个非常有趣的问题,但答案不应该太复杂。有一个很棒的article on medium可以回答这个问题。

1)如果你这样做

this.setState({ a: true });
this.setState({ b: true });

由于batching,我认为a truebfalseb

但是,如果a依赖于// assuming this.state = { value: 0 }; this.setState({ value: this.state.value + 1}); this.setState({ value: this.state.value + 1}); this.setState({ value: this.state.value + 1}); ,那么确实可能存在您无法获得预期状态的情况。

this.state.value

处理完所有上述调用后,setState accepts a function as its parameter将为1,而不是您预期的3。

文章中提到了这一点:// assuming this.state = { value: 0 }; this.setState((state) => ({ value: state.value + 1})); this.setState((state) => ({ value: state.value + 1})); this.setState((state) => ({ value: state.value + 1}));

this.state.value === 3

这会给我们local.properties

答案 2 :(得分:3)

,如doc

  

setState()排队对组件状态的更改并告诉React   这个组件及其子组件需要重新呈现   更新状态。这是用于更新用户的主要方法   接口响应事件处理程序和服务器响应。

它将在队列中预先形成更改( FIFO :先进先出),第一次调用将首先进行预成型

答案 3 :(得分:2)

同一周期内的多个呼叫可以一起批处理。例如,如果您尝试在同一周期中多次增加项目数量,则会产生相当于:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

https://reactjs.org/docs/react-component.html