react通过new MessageChannel()创建了消息通道,当发现js线程空闲时,通过postMessage通知scheduler开始调度。然后react接收到调度开始的通知时,就通过performWorkUntilDeadline函数去更新当前帧的结束时间,以及执行任务。从而实现了帧空闲时间的任务调度。
// packages/scheduler/src/forks/SchedulerHostConfig.default.js
// 获取当前设备每帧的时长
forceFrameRate = function(fps) {
// ...
if (fps > 0) {
yieldInterval = Math.floor(1000 / fps);
} else {
yieldInterval = 5;
}
};
// 帧结束前执行任务
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// 更新当前帧的结束时间
deadline = currentTime + yieldInterval;
const hasTimeRemaining = true;
try {
const hasMoreWork = scheduledHostCallback(
hasTimeRemaining,
currentTime,
);
// 如果还有调度任务就执行
if (!hasMoreWork) {
isMessageLoopRunning = false;
scheduledHostCallback = null;
} else {
// 没有调度任务就通过 postMessage 通知结束
port.postMessage(null);
}
} catch (error) {
// ..
throw error;
}
} else {
isMessageLoopRunning = false;
}
needsPaint = false;
};
// 通过 MessageChannel 创建消息通道,实现任务调度通知
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
// 通过 postMessage,通知 scheduler 已经开始了帧调度
requestHostCallback = function(callback) {
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
port.postMessage(null);
}
};
任务中断
前面说到可中断模式下的workLoop,每次遍历执行performUnitOfWork前会先判断shouYield的值
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
再看一下shouYield的值是如何获取的:
//packages\scheduler\src\SchedulerPostTask.js
export function unstable_shouldYield() {
return getCurrentTime() >= deadline;
}
getCurrentTime获取的是当前的时间戳,deadline上面讲到了是浏览器每一帧结束的时间戳。也就是说concurrent模式下,react会将这些非同步任务放到浏览器每一帧空闲时间段去执行,若每一帧结束未执行完,则中断当前任务,待到浏览器下一帧空闲再继续执行。
总结react render阶段的设计思想
- 当发生渲染或者更新操作时,react去创建一系列的任务,任务带有优先级,然后构建workInProgress fiber树链表。
- 遍历任务链表去执行任务。每一帧帧先执行浏览器的渲染等任务,如果当前帧还有空闲时间,则执行任务,直到当前帧的时间用完。如果当前帧已经没有空闲时间,就等到下一帧的空闲时间再去执行。如果当前帧没有空闲时间但是当前任务链表有任务到期了或者立即基执行任务,那么必须执行的时候就以丢失几帧的代价,执行这些任务。执行完的任务都会被从链表中删除。
执行过程中的流程图如下: