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阶段的设计思想
执行过程中的流程图如下: