React 通过异步合并更新,提高渲染速度。
RequestIdleCallback 的不足
- 兼容性差-RequestIdleCallback 并不是对所有浏览器都支持的。
- 工作帧率低,只有 20FPS,
SetTimeout 是最后的兜底
setTimeout 精确度不够,即使你把延迟时间设置成 0,实际上还会有 4ms 的延迟时间,只能作最后的兜底。
setTimeout(() => {
// 执行异步操作
}, 0);
如何使用 MessageChannel?
入口函数 requestHostCallback:
// 是否有其他异步任务在运行(异步任务指的是 React 创建的异步任务)
// 只有一个主线程,每次只能执行一个任务
// isMessageLoopRunning 类似一把锁,没有任务执行时是 false
// 当要执行一个任务 A 时,isMessageLoopRunning 被设置为 true,防止其他任务执行
// 执行完任务 A,再把 isMessageLoopRunning 设置为 false,才可继续执行任务 B。
let isMessageLoopRunning = false;
function requestHostCallback() {
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
// 执行 work
schedulePerformWorkUntilDeadline();
}
}
使用 MessageChanne 创建宏任务,实现异步任务队列,以实现异步更新,确保 React 在执行更新时能够合并多个更新操作,并在下一个宏任务中一次性更新,以提高性能并减少不必要的重复渲染,从而提高页面性能和用户体验。
const channel = new MessageChannel();
const port = channel.port2;
// 监听
channel.port1.onmessage = performWorkUntilDeadline;
// 在一个时间切片内执行,直到时间切片结束
function schedulePerformWorkUntilDeadline() {
// 执行 work,即发送消息
port.postMessage(null);
}
function performWorkUntilDeadline() {
if (isMessageLoopRunning) {
const currentTime = getCurrentTime();
// 记录了一个 work 的起始时间,其实就是一个时间切片的起始时间,这是个时间戳
// 是否结束通过 shouldYieldToHost 函数判断
startTime = currentTime;
let hasMoreWork = true;
try {
hasMoreWork = flushWork(currentTime);
} finally {
if (hasMoreWork) {
// 有更多的任务
schedulePerformWorkUntilDeadline();
} else {
// 没有任务,这轮 work 结束
isMessageLoopRunning = false;
}
}
}
}
function flushWork(initialTime: number) {
// 结束主线程调度
isHostCallbackScheduled = false;
// 开始执行任务
isPerformingWork = true;
try {
return workLoop(initialTime);
} finally {
currentTask = null;
// 结束执行任务
isPerformingWork = false;
}
}
其中 workLoop 函数和 shouldYieldToHos 函数参考文章 认识 React 中的时间切片。