title: setState desc: React中, setState是同步还是异步
order: 1
React 中, setState 是同步还是异步
所谓同步还是异步指的是调用 setState 之后是否马上能得到最新的 state
不仅仅是setState了, 在对 function 类型组件中的 hook 进行操作时也是一样, 最终决定setState是同步渲染还是异步渲染的关键因素是ReactFiberWorkLoop工作空间的执行上下文.
具体代码如下:
export function scheduleUpdateOnFiber(fiber: Fiber,expirationTime: ExpirationTime,) {const priorityLevel = getCurrentPriorityLevel();if (expirationTime === Sync) {if (// Check if we're inside unbatchedUpdates(executionContext & LegacyUnbatchedContext) !== NoContext &&// Check if we're not already rendering(executionContext & (RenderContext | CommitContext)) === NoContext) {performSyncWorkOnRoot(root);} else {ensureRootIsScheduled(root);schedulePendingInteractions(root, expirationTime);if (executionContext === NoContext) {// 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.flushSyncCallbackQueue();}}} else {// Schedule a discrete update but only if it's not Sync.if ((executionContext & DiscreteEventContext) !== NoContext &&// Only updates at user-blocking priority or greater are considered// discrete, even inside a discrete event.(priorityLevel === UserBlockingPriority ||priorityLevel === ImmediatePriority)) {// This is the result of a discrete event. Track the lowest priority// discrete update per root so we can flush them early, if needed.if (rootsWithPendingDiscreteUpdates === null) {rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);} else {const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);if (lastDiscreteTime === undefined ||lastDiscreteTime > expirationTime) {rootsWithPendingDiscreteUpdates.set(root, expirationTime);}}}// Schedule other updates after in case the callback is sync.ensureRootIsScheduled(root);schedulePendingInteractions(root, expirationTime);}}
可以看到, 是否同步渲染调度决定代码是flushSyncCallbackQueue(). 进入该分支的条件:
- 必须是
legacy模式,concurrent模式下expirationTime不会为Sync executionContext === NoContext, 执行上下文必须要为空.
两个条件缺一不可.
结论
同步:
- 首先在
legacy模式下 - 在执行上下文为空的时候去调用
setState- 可以使用异步调用如
setTimeout,Promise,MessageChannel等 - 可以监听原生事件, 注意不是合成事件, 在原生事件的回调函数中执行 setState 就是同步的
- 可以使用异步调用如
异步:
- 如果是合成事件中的回调,
executionContext |= DiscreteEventContext, 所以不会进入, 最终表现出异步 - concurrent 模式下都为异步
演示示例
import React from 'react';export default class App extends React.Component {state = {count: 0,};changeState = () => {const newCount = this.state.count + 1;this.setState({count: this.state.count + 1,});if (newCount === this.state.count) {console.log('同步执行render');} else {console.log('异步执行render');}};changeState2 = () => {const newCount = this.state.count + 1;Promise.resolve().then(() => {this.setState({count: this.state.count + 1,});if (newCount === this.state.count) {console.log('同步执行render');} else {console.log('异步执行render');}});};render() {return (<div><p>当前count={this.state.count}</p><button onClick={this.changeState}>异步+1</button><button onClick={this.changeState2}>同步+1</button></div>);}}
在看一个 concurrent 模式下的例子, 相同的代码都为异步 render:
