代码之家  ›  专栏  ›  技术社区  ›  darksmurf

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

  •  179
  • darksmurf  · 技术社区  · 6 年前

    我知道React可以异步和批量执行状态更新,以优化性能。因此,您永远不能相信状态在调用后会被更新 setState . 但是你能相信吗 按照与相同的顺序更新状态 设置状态 被称为 对于

    1. 相同的组件?
    2. 不同的组件?

    考虑单击以下示例中的按钮:

    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中对第一个状态更新的回调中执行第二个状态更新。然而,这不是我的问题,我只关心是否有一种定义良好的方式来执行这些状态更新,而不是其他方式。

    任何有文档支持的答案都将不胜感激。

    4 回复  |  直到 6 年前
        1
  •  427
  •   Dan Abramov    2 年前

    我在React上工作。

    TLDR:

    但是您是否可以信任React以与调用setState相同的顺序更新状态

    • 相同的组件?

    • 不同的组件?

    这个 顺序 始终尊重更新的数量。您是否看到它们之间的中间状态取决于您是否在批中。

    在React 17及更早版本中, 默认情况下,仅批处理React事件处理程序中的更新 . 当您需要时,有一个不稳定的API可以强制在事件处理程序之外批处理罕见的情况。

    从React 18开始,React batches all updates by default . 请注意,React永远不会批处理来自两个不同故意事件(如单击或键入)的更新,因此,例如,两个不同的按钮单击永远不会批处理。在极少数不需要分批的情况下,可以使用 flushSync .


    理解这一点的关键是 不管有多少 setState() 调用您要执行的组件数 在React事件处理程序中 ,它们将在事件结束时仅生成一次重新渲染 . 这对于大型应用程序的良好性能至关重要,因为如果 Child Parent 每次通话 设置状态() 处理单击事件时,不希望重新渲染 小孩 两次

    在你的两个例子中, 设置状态() 调用发生在React事件处理程序中。因此,它们总是在事件结束时一起刷新(您看不到中间状态)。

    更新总是 按照发生的顺序进行浅合并 . 所以如果第一次更新是 {a: 10} ,第二个是 {b: 20} ,第三个是 {a: 30} ,渲染状态将为 {a: 30, b: 20} . 同一状态密钥的最新更新(例如 a 在我的例子中)总是“赢”。

    这个 this.state 对象在批处理结束时重新呈现UI时更新。因此,如果需要基于以前的状态更新状态(例如递增计数器),则应使用函数 setState(fn) 提供先前状态的版本,而不是从 这状态 . 如果你对其原因感到好奇,我会详细解释 in this comment .


    在您的示例中,我们不会看到“中间状态”,因为我们是 在React事件处理程序中 启用批处理的位置(因为React“知道”何时退出该事件)。

    然而,在React 17和早期版本中, 默认情况下,在React事件处理程序之外没有批处理 . 因此,如果在您的示例中,我们有一个AJAX响应处理程序,而不是 handleClick 每个 设置状态() 将立即进行处理。在这种情况下,是的,你 请参阅React 17和更早版本中的中间状态:

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

    我们意识到这很不方便 根据您是否在事件处理程序中,行为会有所不同 . 在React 18中,这不再是必要的,但在此之前, 有一个API可以用来强制批处理 :

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

    内部React事件处理程序都打包在 unstable_batchedUpdates 这就是为什么默认情况下会对它们进行批处理。请注意,在中包装更新 不稳定的\u批更新 两次都没有效果。当我们退出最外层时,更新被刷新 不稳定的\u批更新 呼叫

    这个API是“不稳定的”,因为我们最终会在18岁以后(19岁或更高)的一些主要版本中删除它。如果在某些情况下需要在React事件处理程序之外强制批处理,则在React 18之前可以安全地依赖它。使用React 18,您可以删除它,因为它不再有任何效果。


    总之,这是一个令人困惑的话题,因为默认情况下,React只用于事件处理程序内部的批处理。但解决办法不是 无批次 ,是为了 批量更多 默认情况下。这就是我们在React 18中所做的。

        2
  •  9
  •   Michal    5 年前

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

    1) 如果你这样做

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

    我不认为会有这样的情况 a true b false 因为 batching .

    然而 如果 b 取决于 然后,确实可能会出现无法获得预期状态的情况。

    // 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 将是1,而不是你所期望的3。

    文章中提到了这一点: setState accepts a function as its parameter

    // 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

        3
  •  4
  •   Mosè Raguzzini    6 年前

    同一周期内的多个调用可以批处理在一起。例如,如果您试图在同一周期内多次增加一个物料数量,则将导致等同于:

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

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

        4
  •  3
  •   Ali Faris    6 年前

    如中所示 doc

    设置状态() 排队等候 更改组件状态并告知React 此组件及其子组件需要使用 已更新状态。这是用于更新用户的主要方法 接口以响应事件处理程序和服务器响应。

    它将在队列中执行更改( 先进先出 :先进先出)第一次调用将首先执行