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

我知道 React 可以异步和批量地执行状态更新,以便进行性能优化。因此,您永远不能相信在调用 setState之后要更新的状态。但是你能相信 按调用 setState的顺序更新状态的反应吗

  1. 相同的成分?
  2. 不同的成分?

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

有没有可能 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 是否有一种定义良好的方式来执行这些状态更新感兴趣,没有别的。

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

67744 次浏览

同一周期内的多个调用可以批处理在一起。例如,如果您试图在同一个循环中多次递增一个项目的数量,那么将导致等效于:

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

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

这实际上是一个非常有趣的问题,但是答案不应该太复杂。有这个伟大的 介质上的文章有一个答案。

1)如果你这样做

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

我不认为会有这样的情况,a将是 trueb将是 false,因为 配料

然而,如果 ABC0依赖于 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将是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

就是 医生

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

它将预先形成队列中的变化(FIFO: 先进先出)第一个调用将是第一个预先形成

我在“反应”工作。

TLDR:

但是您能够信任 React 按照调用 setState 的顺序更新状态吗

  • 相同的成分?

是的。

  • 不同的成分?

是的。

更新的 秩序总是受到尊重的。你是否看到他们之间的居间态取决于你是否在一个批次中。

在反应17和更早的时候,默认情况下,只批处理 React 事件处理程序中的更新。有一个不稳定的 API,可以在需要时强制在事件处理程序之外进行批处理。

从反应18开始,反应 默认情况下批处理所有更新。请注意,React 永远不会批处理来自两个不同意图事件(如单击或键入)的更新,因此,例如,两个不同的按钮单击永远不会批处理。在不需要批处理的极少数情况下,可以使用 flushSync


理解这一点的关键是 无论在 React 事件处理程序 中执行了多少次 setState()调用 < em > ,它们都只会在事件结束时产生一次重新呈现。这对于大型应用程序的良好性能至关重要,因为在处理单击事件时,如果 ChildParent各自调用 setState(),则不希望重新呈现 Child两次。

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

更新总是 按出现顺序浅合并。因此,如果第一个更新是 {a: 10},第二个是 {b: 20},第三个是 {a: 30},呈现的状态将是 {a: 30, b: 20}。最近对同一个状态键的更新(例如,在我的示例中,类似于 a)总是“获胜”。

当我们在批处理结束时重新呈现 UI 时,将更新 this.state对象。因此,如果需要根据以前的状态更新状态(比如递增计数器) ,应该使用提供以前状态的函数式 setState(fn)版本,而不是从 this.state读取。如果你对其中的原因感到好奇,我在 在这个评论中中深入地解释了它。


在您的示例中,我们不会看到“居间态”,因为我们是启用了批处理的 在 React 事件处理程序中(因为当我们退出事件时 React“ know”)。

然而,在反应17和更早的版本,默认情况下,在 React 事件处理程序之外没有批处理。因此,如果在您的示例中,我们使用了 AJAX 响应处理程序而不是 handleClick,那么每个 setState()将在发生时立即得到处理。在这种情况下,是的,你在反应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
});

我们意识到 根据您是否处于事件处理程序中,行为是不同的不方便。在反应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中,这就是为什么默认情况下它们是批处理的。请注意,在 unstable_batchedUpdates中两次包装更新没有任何效果。当我们退出最外层的 unstable_batchedUpdates调用时,将刷新更新。

这个 API 是“不稳定的”,因为我们最终会在18之后的某个主要版本中删除它(19或者更多)。如果您需要在 React 事件处理程序之外的某些情况下强制批处理,那么您可以安全地依赖它,直到 React 18。对于反应18,你可以移除它,因为它已经没有任何效果了。


总之,这是一个令人困惑的主题,因为 React 在默认情况下只在事件处理程序内部批处理。但是解决方案不是对 少批次,而是默认情况下对 再来一批。这就是我们在18号反应中所做的。