setState 是同步还是异步的

知识点:

setState 为什么是异步执行?

  • 在生命周期和合成事件中,React 为了性能优化开启了批量更新,此时 setState 只会被放到更新队列,不会马上执行,而是稍后一起批量更新,所以表现为异步的。
  • 在 setTimeout/setInterval/addEventListener/Promsie 中,因为脱离了 React 的掌控,所以表现为同步。

在我们使用 class 组件时,基本都会使用到 setState,因为唯有使用 setState 更新数据,才能实现数据驱动视图的效果。

同步还是异步

我们先来看 setState 是同步的还是异步的:
1、依次点击“点我增加”、“点我增加三倍”和“点我减少”,在控制台可以看到以下输出。

1
2
3
4
5
6
7
8
9
10
increment setState前的count 0
increment setState后的count 0


triple setState前的count 1
triple setState后的count 1


reduce setState前的count 2
reduce setState后的count 1

2、分析下结果

  • 点击“点我增加”,setState 前后打印 count 都为 0,说明是异步更新;
  • 点击“点我增加三倍”, setState 前后打印 count 皆为 1,说明多来几个 setState,React 也是异步的,而且是批量一起的。
  • 点击“点我减少”,setState 前为 2,setState 后为 1,说明 setState 生效,说明 setTimeout 下,setState 是同步的。

3、结论
setState 不是简单的同步或者异步,在不同场景下 setState 会表现不同。在 React 生命周期钩子函数和合成事件中,setState 表现为异步;在 setTimeout、setInterval 等函数中,包括在 DOM 原生事件中,它都表现为同步。这种差异本质上是 React 的事务机制和批量更新机制决定的。

setState 更新过程

setState 源码的更新过程如下:
1.setState 执行进入 enqueueSetState 函数
2.enqueueSetState 进入 enqueueUpdate
3.enqueueUpdate 通过 isBatchingUpdate 判断当前是否在批量更新中。如果是,把组件加入 dirtyComponents 进行更新;否则,开始批量更新。

setState 更新过程

同步与异步原因

因为生命周期钩子函数和合成事件执行之前,React 都会执行 batchUpdate,此时 isBatchingUpdate 被设为 true。执行 setState 操作的组件进入批量的 dirtyComponents 中等待更新,setState 在代码执行上是同步,但由于进入等待队列进行批量更新,表现为异步。

而在 setTimeout 和 setInterval 中,没有执行 batchUpdate,所以表现为同步更新。

参考资料