我知道React可以异步和批量执行状态更新以进行性能优化。因此,在调用setState
后,您永远不能相信要更新的状态。但是你能相信React能够以setState
被称为的相同顺序更新状态
考虑点击以下示例中的按钮:
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执行这些状态更新的明确方式,没有别的。
非常感谢文档支持的任何答案。
答案 0 :(得分:253)
我在React工作。
<强> TLDR:强>
但是你能相信React以与调用setState相同的顺序更新状态吗
- 相同的组件?
是
- 不同的组件?
是
始终遵守更新的顺序。你是否看到了一个中间状态&#34;之间的#34;他们与否取决于你是否批量进入。
目前(React 16及更早版本),默认情况下仅对React事件处理程序内的更新进行批处理。在您需要时,有一个不稳定的API可以强制在事件处理程序之外进行批处理。
在未来版本(可能是React 17及更高版本)中,React默认会批量处理所有更新,因此您不必考虑这一点。与往常一样,我们会在React blog和发行说明中公布对此的任何更改。
理解这一点的关键是无论你在React事件处理程序中执行了多少个{em>>调用,它们只产生一个re-在活动结束时呈现。这对于大型应用程序的良好性能至关重要,因为如果setState()
和Child
在处理点击事件时每次调用Parent
,则您不想重新呈现{{1}两次。
在两个示例中,setState()
调用都发生在React事件处理程序中。因此,它们总是在事件结束时被冲洗在一起(你不会看到中间状态)。
更新始终按其出现的顺序浅层合并。因此,如果第一次更新为Child
,第二次更新为setState()
,第三次更新为{a: 10}
,则呈现状态为{b: 20}
。对同一状态键的更新更新(例如,在我的示例中类似{a: 30}
)总是&#34;赢得&#34;。
当我们在批处理结束时重新呈现UI时,{a: 30, b: 20}
对象会更新。因此,如果您需要根据以前的状态更新状态(例如递增计数器),则应使用功能a
版本来提供先前的状态,而不是从this.state
读取。如果您对此的理由感到好奇,我会深入解释in this comment。
在你的例子中,我们不会看到&#34;中间状态&#34;因为我们在React事件处理程序中启用了批处理(因为React&#34;知道&#34;当我们退出该事件时)。
但是,在React 16和早期版本中,默认情况下,在React事件处理程序之外没有默认批处理。因此,如果在您的示例中我们有一个AJAX响应处理程序而不是setState(fn)
,则每个this.state
都会在发生时立即处理。在这种情况下,是的,您会看到中间状态:
handleClick
我们发现行为不同取决于您是否处于事件处理程序中,这是不方便的。这将在未来的React版本中更改,默认情况下将批量处理所有更新(并提供一个选择性API以同步刷新更改)。在我们切换默认行为之前(可能在React 17中),有一个可用于强制批处理的API :
setState()
内部React事件处理程序都包含在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
});
中,这就是默认情况下它们被批量处理的原因。请注意,在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
});
中包装更新两次无效。当我们退出最外面的unstable_batchedUpdates
呼叫时,刷新更新。
该API不稳定&#34;在默认情况下已经启用批处理时我们将删除它。但是,我们不会在次要版本中删除它,所以如果你需要在React事件处理程序之外的某些情况下强制批处理,你可以安全地依赖它直到React 17。
总而言之,这是一个令人困惑的主题,因为React默认只在事件处理程序中批处理。这将在未来版本中发生变化,然后行为将变得更加直截了当。但解决方案不是批量减少,默认情况下批量更多。这就是我们要做的事情。
答案 1 :(得分:3)
这实际上是一个非常有趣的问题,但答案不应该太复杂。有一个很棒的article on medium可以回答这个问题。
1)如果你这样做
this.setState({ a: true });
this.setState({ b: true });
由于batching,我认为a
true
和b
将false
为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
处理完所有上述调用后,setState accepts a function as its parameter
将为1,而不是您预期的3。
文章中提到了这一点:// 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
这会给我们local.properties
答案 2 :(得分:3)
,如doc
setState()排队对组件状态的更改并告诉React 这个组件及其子组件需要重新呈现 更新状态。这是用于更新用户的主要方法 接口响应事件处理程序和服务器响应。
它将在队列中预先形成更改( FIFO :先进先出),第一次调用将首先进行预成型
答案 3 :(得分:2)
同一周期内的多个呼叫可以一起批处理。例如,如果您尝试在同一周期中多次增加项目数量,则会产生相当于:
Object.assign(
previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)