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

IT技术 javascript reactjs state setstate
2021-02-06 17:56:04

我知道 React 可能会异步和批量执行状态更新以进行性能优化。因此,您永远不能相信在调用setState. 但是你可以信任的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 中对第一个状态更新的回调中执行第二个状态更新。但是,这不是我的问题,我只对 React 执行这些状态更新是否有明确定义的方式感兴趣,没有别的。

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

4个回答

我在 React 工作。

域名注册地址:

但是你能相信 React 会按照调用 setState 的顺序更新状态吗?

  • 相同的组件?

是的。

  • 不同的组件?

是的。

订单的更新总是尊重。您是否看到它们“之间”的中间状态取决于您是否在批处理中。

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

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


理解这一点的关键是,无论setState()在 React 事件处理程序中对多少个组件进行了多少次调用,它们都只会在事件结束时产生一次重新渲染这对于大型应用程序性能良好至关重要的,因为如果ChildParent每个呼叫setState()处理click事件的时候,你不想再渲染Child两次。

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

更新总是按照它们发生的顺序浅合并所以如果第一次更新是{a: 10},第二次是{b: 20},第三次是{a: 30},渲染状态将是{a: 30, b: 20}对相同状态密钥的更新(例如a在我的示例中)总是“获胜”。

this.state当我们在批处理结束时重新渲染 UI 时对象会更新。因此,如果您需要根据先前的状态更新状态(例如增加计数器),则应使用setState(fn)提供先前状态的功能版本,而不是从this.state. 如果您对此原因感到好奇,我在此评论中对其进行了深入解释


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

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

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 版本中更改,默认情况下将批处理所有更新(并提供选择加入的 API 以同步刷新更改)。在我们切换默认行为之前(可能在 React 17 中),您可以使用一个 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 是“不稳定的”,因为我们将在默认情况下已启用批处理时将其删除。但是,我们不会在次要版本中删除它,因此如果您需要在 React 事件处理程序之外的某些情况下强制批处理,您可以安全地依赖它直到 React 17。


总而言之,这是一个令人困惑的话题,因为 React 默认只在事件处理程序中进行批处理。这将在未来版本中改变,届时行为将更加直接。但解决方案不是批量减少,而是默认批量增加这就是我们要做的。

“始终获得正确顺序”的一种方法是创建一个临时对象,分配不同的值(例如obj.a = true; obj.b = true),然后在最后执行this.setState(obj)无论您是否在事件处理程序中,这都是安全的。如果您经常发现自己犯了在事件处理程序之外多次设置状态的错误,这可能是一个巧妙的技巧。
2021-03-13 17:56:04
您能否在这篇文章中澄清“React 事件处理程序”是否包含componentDidUpdate自 React 16 起的其他生命周期回调?提前致谢!
2021-03-25 17:56:04
这个答案应该添加到 reactjs.org 文档中
2021-03-29 17:56:04
因此,我们实际上不能依赖批处理仅限于一个事件处理程序 - 正如您明确指出的,至少因为这种情况不会很快发生。然后我们应该使用 setState 和 updater 函数来访问最近的状态,对吗?但是如果我需要使用一些 state.filter 来创建一个 XHR 来读取一些数据,然后将它们放入 state 呢?看起来我必须将带有延迟回调(因此会产生副作用)的 XHR 放入更新程序中。那么,这被认为是最佳实践吗?
2021-04-05 17:56:04
顺便说一句,这也意味着我们根本不应该从 this.state 中读取;读取某些 state.X 的唯一合理方法是在 updater 函数中从其参数中读取它。而且,写入 this.state 也是不安全的。那为什么要允许访问 this.state 呢?这些可能应该是独立的问题,但主要是我只是想了解我的解释是否正确。
2021-04-09 17:56:04

这实际上是一个非常有趣的问题,但答案不应太复杂。一篇关于媒体的伟大文章有一个答案。

1)如果你这样做

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

我不认为会出现的情况下atruebfalse因为配料

但是,如果b依赖于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

@Michal,嗨,Michal 只是想问一个问题,如果我们有 this.setState({ value: 0}); 是真的吗?this.setState({ 值: this.state.value + 1}); 第一个 setState 将被忽略,只有第​​二个 setState 会被执行?
2021-03-28 17:56:04
如果有什么this.state.value在事件处理程序(其中更新都setState被批量)和AJAX回调(其中setState不是批处理)。在事件处理程序中,我将使用updater function来确保我使用函数提供的当前更新状态更新状态。setState即使我知道它不是批处理的,我是否应该在 AJAX 回调的代码中使用更新程序函数?您能否澄清使用setState或不使用更新程序函数在 AJAX 回调中的用法谢谢!
2021-04-01 17:56:04
@Dickens 我相信两者setState都会被执行,但最后一个会赢。
2021-04-04 17:56:04

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

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

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

就像在文档中一样

setState()组件状态的更改加入队列,并告诉 React 该组件及其子组件需要使用更新后的状态重新渲染。这是您用来更新用户界面以响应事件处理程序和服务器响应的主要方法。

它将按照队列(FIFO:先进先出)执行更改,第一个调用将首先执行

嗨阿里,只是想问一个问题,如果我们有 this.setState({ value: 0}); this.setState({ 值: this.state.value + 1}); 第一个 setState 将被忽略,只有第​​二个 setState 会被执行?
2021-04-08 17:56:04