【问题标题】:Does React keep the order for state updates?React 是否保持状态更新的顺序?
【发布时间】:2018-07-11 19:57:03
【问题描述】:

我知道 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 执行这些状态更新的定义明确的方式,仅此而已。

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

【问题讨论】:

  • 这似乎不是一个毫无意义的问题,你也可以在 react page 的 github issues 上问这个问题,dan abramov 通常在那里很有帮助。当我有这么棘手的问题时,我会问,他会回答。糟糕的是,这些问题不像官方文档那样公开共享(因此其他人也可以轻松访问它)。我也觉得 React 官方文档缺乏对某些主题的广泛报道,例如您的问题中的主题等。
  • 例如:github.com/facebook/react/issues/11793,我相信该问题中讨论的内容会对许多开发人员有所帮助,但这些内容不在官方文档中,因为 FB 人认为这是高级的。其他事情可能也是如此。我认为一篇题为“深入反应的状态管理”或“状态管理的陷阱”之类的官方文章探讨了您问题中状态管理的所有角落案例,这还不错。也许我们可以推动 FB 开发人员用这些东西扩展文档:)
  • 我的问题中有一篇关于媒体的精彩文章的链接。它应该涵盖 95% 的状态用例。 :)
  • @Michal 但那篇文章仍然没有回答这个问题恕我直言

标签: javascript reactjs state setstate


【解决方案1】:

我在 React 上工作。

TLDR:

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

  • 相同的组件?

是的。

  • 不同的组件?

是的。

始终遵守更新的顺序。您是否看到它们“之间”的中间状态取决于您是否在批次中。

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

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


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

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

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

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


在您的示例中,我们不会看到“中间状态”,因为我们在启用批处理的 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 仅在事件处理程序中进行批处理。这将在未来的版本中发生变化,并且行为将更加直接。但解决方案不是batch less,而是默认batch more。这就是我们要做的。

【讨论】:

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

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

1) 如果你这样做

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

我认为不会出现a 会变成trueb 会变成false 的情况,因为batching

但是,如果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

【讨论】:

  • 如果this.state.value 在事件处理程序(setState 被批处理)和 AJAX 回调(setState批处理)中更新会怎样。在事件处理程序中,我会使用updater function 来始终确保我使用函数提供的当前更新状态来更新状态。我是否应该在 AJAX 回调的代码中使用 setState 和更新程序函数,即使我知道它没有被批处理?您能否澄清 setState 在使用或不使用更新程序函数的 AJAX 回调中的用法?谢谢!
  • @Michal,嗨,Michal 只是想问一个问题,如果我们有 this.setState({ value: 0}); 是真的吗? this.setState({ value: this.state.value + 1});第一个 setState 会被忽略,只执行第二个 setState?
  • @Dickens 我相信setState 都会被执行,但最后一个会赢。
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

    doc

    setState() enqueues 改变组件状态并告诉 React 这个组件及其子组件需要重新渲染 更新状态。这是您用来更新用户的主要方法 响应事件处理程序和服务器响应的接口。

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

    【讨论】:

    • 嗨阿里,只是想问一个问题,如果我们有 this.setState({ value: 0}); 是真的吗? this.setState({ value: this.state.value + 1});第一个 setState 会被忽略,只执行第二个 setState?
    猜你喜欢
    • 1970-01-01
    • 2020-10-07
    • 1970-01-01
    • 1970-01-01
    • 2017-09-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多