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


139

我知道React可以异步并批量执行状态更新以优化性能。因此,在调用以后,您将永远无法相信要更新的状态setState。但是你可以信任的反应更新相同的顺序状态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是否有明确定义的方式来执行这些状态更新感兴趣,仅此而已。

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



3
这似乎不是一个毫无意义的问题,您也可以在react页面的github问题上问这个问题,dan abramov通常在那里很有帮助。当我有这么棘手的问题时,我会问,他会回答。不好的是,这类问题没有像官方文档那样公开共享(以便其他人也可以轻松访问)。我也感到React官方文档缺乏对某些主题的广泛报道,例如您所提问题的主题等
。– giorgim

例如:github.com/facebook/react/issues/11793,我相信该问题中讨论的内容对许多开发人员会有所帮助,但是这些内容不在官方文档中,因为FB人士认为这是高级的。其他事情可能也一样。我认为官方文章标题为“深入进行状态管理”或“状态管理的陷阱”的文章,就像您所质疑的那样,探索状态管理的所有极端情况都是不错的。也许我们可以推动FB开发人员使用此类内容扩展文档:)
giorgim

我想问的是有关媒介的出色文章的链接。它应该覆盖95%的州用例。:)
Michal '18

2
@Michal,但那篇文章仍然没有回答这个问题恕我直言
giorgim

Answers:


335

我在做React。

TLDR:

但是您可以信任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仅在事件处理程序中进行批处理。这将在将来的版本中更改,并且行为将变得更加简单。但是解决方案不是少批处理,而是默认情况下多批处理。那就是我们要做的。


1
一种“始终正确处理订单”的方法是创建一个临时对象,分配不同的值(例如obj.a = true; obj.b = true),然后最后执行do this.setState(obj)。不管您是否在事件处理程序中,这都是安全的。如果您经常发现自己在事件处理程序之外多次犯错了设置状态的错误,那可能是个好主意。
克里斯,

因此,我们实际上不能仅仅依赖于批处理将事件限于一个事件处理程序-正如您已经明确指出的那样,至少是因为很快就不会如此。然后,我们应该将setState与updater函数一起使用来访问最新状态,对吗?但是,如果我需要使用一些state.filter来使XHR读取一些数据,然后将其置于状态,该怎么办?看来我将不得不将具有延迟回调(并因此产生副作用)的XHR放入更新程序中。那被认为是最佳实践吗?
Maksim Gumerov '18

1
顺便说一句,这也意味着我们根本不应该阅读this.state;读取某些状态的唯一合理方法。X是在updater函数中从其参数读取它。而且,写入this.state也是不安全的。那么,为什么要完全允许访问this.state?这些也许应该提出独立的问题,但是大多数情况下,我只是想了解我是否理解正确。
Maksim Gumerov

10
这个答案应该添加到reactjs.org文档中
Deen John

2
您能否在这篇文章中阐明componentDidUpdate,React 16中是否包含“ React事件处理程序” 和其他生命周期回调?提前致谢!
伊万

6

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

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


如果有什么this.state.value在事件处理程序(其中更新都setState被批量)和AJAX回调(其中setState不是批处理)。在事件处理程序中,我将updater function始终使用来确保使用该函数提供的当前更新状态来更新状态。setState即使我知道未批处理,我是否应该在AJAX回调的代码内使用updater函数?您能否说明setState在AJAX回调中使用或不使用更新程序功能的用法?谢谢!
tonix

@Michal,您好Michal只是想问一个问题,如果我们有this.setState({value:0});是真的吗?this.setState({value:this.state.value + 1}); 第一个setState将被忽略,仅第二个setState将被执行?
狄更斯

@狄更斯我相信两个setState都会被处决,但最后一个会赢。
Michal


3

文档中

的setState()入列更改组件状态并告诉阵营,这个组件和它的孩子需要重新渲染与更新的状态。这是用于响应事件处理程序和服务器响应而更新用户界面的主要方法。

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


嗨,阿里,只想问一个问题,如果我们有this.setState({value:0});是真的吗?this.setState({value:this.state.value + 1}); 第一个setState将被忽略,仅第二个setState将被执行?
狄更斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.