react-this.setState是同步还是异步的

  1. 如果需要依赖更新后的值,如果是classComponent,我们可以在componentDidUpdate中执行,或者this.setState的第二个参数。如果是functionComponent,我们可以在useEffect中执行
  2. 不同模式下的 react,这个答案是不一样的

本文讨论第二点

react 的三种模式:

  • legacy,react 当前使用的模式,ReactDOM.render
  • blocking,开启部分concurrent模式特性的中间模式
  • concurrent,react v18 启用。任务中断/任务优先级都是针对concurrent模式

lagecy 模式

lagecy 模式下,this.setState触发的更新是异步的。因为它会命中batchedUpdates函数。

batchedUpdates

定义:
batchedUpdates 表示批处理,react 会将多次**this.setState**合并为一次更新,并异步执行。这样只会触发一次render函数,以此提高性能。

实现:
fn:包含this.setState的函数,比如点击事件、生命周期等。
react 内部有个batchedUpdates 函数,在执行这个fn之前,会给全局变量executionContext附加上BatchedContex这个 flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export function batchedUpdates(fn, a) {
var prevExecutionContext = executionContext;
executionContext |= BatchedContext;

try {
return fn(a);
} finally {
executionContext = prevExecutionContext;

if (executionContext === NoContext) {
resetRenderTimer();
flushSyncCallbackQueue();
}
}
}

然后它会执行这个 fn,执行完之后,会把BatchedContexexecutionContext去除。

如果全局变量executionContext包含了BatchedContext,他就会认为这是一次批处理。批处理中的setState会被合并为一次更新。
classComponent 为例
例子一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { PureComponent } from 'react';

export default class MyClassCmp extends PureComponent {
constructor() {
super();
this.state = {
count: 0,
};

this.onAdd = this.onAdd.bind(this);
}

onAdd() {
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
console.log('after', this.state.count); // 输入 0
}

componentDidUpdate() {
console.log('componentDidMount', this.state.count); // 输出 1
}

render() {
const { count } = this.state;
return (
<div>
count: {count}
<button onClick={this.onAdd}>add</button>
</div>
);
}
}

onAdd中调用this.setState,会被认为是批处理,并不能马上获取到最新的count。可以在componentDidUpdate钩子中或者this.setState的第二个参数中获取到最新值。

如何跳出batchedUpdates

如果fn中触发的this.setState是异步执行的话,等this.setState执行的时候,全局的executionContext就已经不存在BatchedContext,他就会跳出批处理。
跳出批处理后,每次调度更新都会执行scheduleUpdateOnFiber函数,函数内部有段逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// react V < 18
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
if (lane === SyncLane) {
// 省略其他代码...

if (executionContext === noContext) {
flushSyncCallbackQueue();
}
}
}

// 这里不用关心,只是单纯了解下18版本源码
// react V18
// flushSyncCallbacksOnlyInLegacyMode 命名更语义化了
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
if (
lane === SyncLane &&
executionContext === NoContext &&
(fiber.mode & ConcurrentMode) === NoMode && // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
!ReactCurrentActQueue$1.isBatchingLegacy
) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
resetRenderTimer();
flushSyncCallbacksOnlyInLegacyMode();
}
}

也就是说,如果executionContext什么都没有的话,会执行fulshSyncCallbackQueue函数,同步的执行这次更新。

例子二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
onAdd () {
// this.setState({ count: this.state.count+1 })
// this.setState({ count: this.state.count+1 })
// console.log('after', this.state.count); // 输入 0

setTimeout(() => {
this.setState({ count: this.state.count+1 })
this.setState({ count: this.state.count+1 })
console.log('after', this.state.count); // 输入 2
})
}

componentDidUpdate() {
console.log('componentDidMount', this.state.count); // 输出两次: 1 2
}

concurrent 模式

ReactDOM.render(<App />, rootNode)变为为ReactDOM.createRoot(rootNode).render(<App />),这样就切换到了concurrent 模式下。
concurrent模式下,例子一的表现结果与laecy模式一致。
例子二,after 的输出 从2变为0componentDidMount里的输出两次1和2 变为输出一次1,这是为什么呢?

这是因为,执行scheduleUpdateOnFiber函数的前提是本次更新的优先级是同步的优先级lane === SyncLane。那什么是同步的优先级呢,也就是ReactDOM.render创建的应用的更新都是同步的优先级,而concurrent模式创建的应用有不同的优先级,所以不会命中fulshSyncCallbackQueue,即同步的更新。

总结

  • lagecy 模式命中 batchedUpdates时异步
  • lagecy 模式未命中batchedUpdates时同步
  • concurrent 模式都是异步
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×