React中setState是同步还是异步的?

“ 上一章节说到了setState的使用和部分源码,这一章节继续上一章节的讲解。setState是同步还是异步的呢?可能你会觉得这个问题有点无聊,但是我们还是需要将他了解清楚。”

ps:在这里我们所说的异步和你心中理解的异步不是相同的定义,在setState中的异步其实是批处理,本质上还是同步执行的,因此不要被误导了。
当然如果你对setState方法的使用还有问题的话,请移至我上一篇文章中:
React中setState的使用和源码分析

1. setState究竟是同步还是异步呢?

答案是需要看情况而定的。像React生命周期、合成事件等的话它就是异步的,而像setTimeout定时器、操作原生DOM事件等它就是同步的。也就是说只要你进入了React的调度流程,那它就是异步的,相反的话如果你没有进入React调度流程那么就是同步的。

2. setState源码中的体现

我们还是去react-reconciler文件夹中找到ReactFiberScheduler.old.js文件,在该文件中找到requestWork函数,具体代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
// 将root加入调度中
addRootToSchedule(root, expirationTime);
if (isRendering) {
return;
}
if (isBatchingUpdates) { // 判断是否需要批量更新
if (isUnbatchingUpdates) { // 判断是否不需要批量更新
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, false);
}
return;
}
// 是同步还是异步,异步的话需要调度
if (expirationTime === Sync) {
performSyncWork(); // 关键点:执行任务队列
} else {
// 这个函数可以让浏览器空闲时期依次调用函数,
// 这就可以让开发者在主事件循环中执行后台或低优先级的任务
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}

注意:在上面代码中,其实可以知道我们所说的setState是“异步”,并不是我们所想的那个异步。setState中的“异步”其实是同步写法,它只是批量处理使代码执行顺序不同,让其有了异步的感觉。
(1)首先对于React中的生命周期函数componentDidMount,它会执行ReactFiberScheduler.old.js文件中的performWorkOnRoot函数,具体代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
function performWorkOnRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
isYieldy: boolean,
) {
invariant(
!isRendering,
'performWorkOnRoot was called recursively. This error is likely caused ' +
'by a bug in React. Please file an issue.',
);
isRendering = true; // 重点
// ...
}

(2)对于React中的合成事件,它经过一系列代码之后会执行isBatchingUpdate为true;
(3)对于setTimeout过程,首先知道setTimeout是异步的,它是宏任务,如果我们在生命周期componentDidMount中添加setTimeout时,此时isBatchingUpdates值就是默认值false,至于expirationTime是否等于Sync,此时就需要知道expirationTime在哪里设置的。
(4)expirationTime的设置需要去react-reconciler文件夹下的ReactFiberClassComponent.js文件中找到enqueueState函数,接着我们就会看到如下代码,然后我们再接着找computeExpirationForFiber函数,然后去ReactFiberScheduler文件中找到该函数。
const expirationTime = computeExpirationForFiber(currentTime, fiber);
(5)在computeExpirationForFiber函数中,我们可以看到如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在这里ConcurrentMode也是一个
if (fiber.mode & ConcurrentMode) {
if (isBatchingInteractiveUpdates) {
expirationTime = computeInteractiveExpiration(currentTime);
} else {
expirationTime = computeAsyncExpiration(currentTime);
}
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
expirationTime -= 1;
}
} else {
expirationTime = Sync;
}
}

在上面代码中我们可以看到ConcurrentMode字段,它其实是新的一个延伸,总共有三种模式:legacy模式、blocking模式、concurrent模式;而我们在react开发中用的是legacy模式,如果你使用的是concurrent模式的话,那么都是异步的。
综上大致说了一下setState是同步还是”异步”的问题:
(1)关于异步更新其实就是setState是批量更新的一个过程,如果你是react生命周期、合成事件的话那么就是”异步”;
(2)如果是定时器、操作原生dom那么setState就是同步更新的;
(3)如果当前模式是concurrent模式的话那么setState也是异步更新的。

如果讲的不好的话请多谅解,谢谢。