本章将讲解 react 的核心阶段之一 —— render阶段,我们将探究以下部分内容的源码:
- 更新任务的触发
- 更新任务的创建
- reconciler 过程同步和异步遍历及执行任务
- scheduler 是如何实现帧空闲时间调度任务以及中断任务的
触发更新的方式主要有以下几种:ReactDOM.render
、setState
、forUpdate
以及 hooks 中的 useState
等,关于 hooks 的我们后面再详细讲解,这里先关注前三种情况。
ReactDOM.render
作为 react 应用程序的入口函数,在页面首次渲染时便会触发,页面 dom 的首次创建,也属于触发 react 更新的一种情况。
首先调用 legacyRenderSubtreeIntoContainer
函数,校验根节点 root 是否存在,若不存在,调用 legacyCreateRootFromDOMContainer
创建根节点 root、rootFiber 和 fiberRoot 并绑定它们之间的引用关系,然后调用 updateContainer
去非批量执行后面的更新流程;若存在,直接调用 updateContainer
去批量执行后面的更新流程:
// packages/react-dom/src/client/ReactDOMLegacy.jsfunction legacyRenderSubtreeIntoContainer(parentComponent: ?React$Component, children: ReactNodeList, container: Container, forceHydrate: boolean, callback: ?Function,
) {// ...let root: RootType = (container._reactRootContainer: any);let fiberRoot;if (!root) {// 首次渲染时根节点不存在// 通过 legacyCreateRootFromDOMContainer 创建根节点、fiberRoot 和 rootFiberroot = container._reactRootContainer = legacyCreateRootFromDOMContainer(container,forceHydrate,);fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function() {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// 非批量执行更新流程unbatchedUpdates(() => {updateContainer(children, fiberRoot, parentComponent, callback);});} else {fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function() {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// 批量执行更新流程updateContainer(children, fiberRoot, parentComponent, callback);}return getPublicRootInstance(fiberRoot);
}
updateContainer
函数中,主要做了以下几件事情:
相关参考视频讲解:进入学习
// packages/react-dom/src/client/ReactDOMLegacy.jsexport function updateContainer(element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component, callback: ?Function,
): Lane {// ...const current = container.current;const eventTime = requestEventTime(); // 获取更新触发的时间// ...const lane = requestUpdateLane(current); // 获取任务优先级if (enableSchedulingProfiler) {markRenderScheduled(lane);}const context = getContextForSubtree(parentComponent);if (container.context === null) {container.context = context;} else {container.pendingContext = context;}// ...const update = createUpdate(eventTime, lane); // 创建更新任务update.payload = {element};callback = callback === undefined ? null : callback;if (callback !== null) {// ...update.callback = callback;}enqueueUpdate(current, update); // 将任务推入更新队列scheduleUpdateOnFiber(current, lane, eventTime); // schedule 进行调度return lane;
}
setState 时类组件中我们最常用的修改状态的方法,状态修改会触发更新流程。
class 组件在原型链上定义了 setState
方法,其调用了触发器 updater
上的 enqueueSetState
方法:
// packages/react/src/ReactBaseClasses.jsComponent.prototype.setState = function(partialState, callback) {invariant(typeof partialState === 'object' ||typeof partialState === 'function' ||partialState == null,'setState(...): takes an object of state variables to update or a ' +'function which returns an object of state variables.',);this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
然后我们再来看以下 updater 上定义的 enqueueSetState
方法,一看到这我们就了然了,和 updateContainer
方法中做的事情几乎一模一样,都是触发后续的更新调度。
// packages/react-reconciler/src/ReactFiberClassComponent.old.jsconst classComponentUpdater = {isMounted,enqueueSetState(inst, payload, callback) {const fiber = getInstance(inst);const eventTime = requestEventTime(); // 获取更新触发的时间const lane = requestUpdateLane(fiber); // 获取任务优先级const update = createUpdate(eventTime, lane); // 创建更新任务update.payload = payload;if (callback !== undefined && callback !== null) {if (__DEV__) {warnOnInvalidCallback(callback, 'setState');}update.callback = callback;}enqueueUpdate(fiber, update); // 将任务推入更新队列scheduleUpdateOnFiber(fiber, lane, eventTime); // schedule 进行调度// ...if (enableSchedulingProfiler) {markStateUpdateScheduled(fiber, lane);}},// ...
};
forceUpdate
的流程与 setState
几乎一模一样:
同样其调用了触发器 updater 上的 enqueueForceUpdate
方法,enqueueForceUpdate
方法也同样是触发了一系列的更新流程:
reconciler/src/ReactFiberClassComponent.old.jsconst classComponentUpdater = {isMounted,// ...enqueueForceUpdate(inst, callback) {const fiber = getInstance(inst);const eventTime = requestEventTime(); // 获取更新触发的时间const lane = requestUpdateLane(fiber); // 获取任务优先级const update = createUpdate(eventTime, lane); // 创建更新update.tag = ForceUpdate;if (callback !== undefined && callback !== null) {if (__DEV__) {warnOnInvalidCallback(callback, 'forceUpdate');}update.callback = callback;}enqueueUpdate(fiber, update); // 将任务推进更新队列scheduleUpdateOnFiber(fiber, lane, eventTime); // 触发更新调度// ...if (enableSchedulingProfiler) {markForceUpdateScheduled(fiber, lane);}},
};
可以发现,上述的三种触发更新的动作,最后殊途同归,都会走上述流程图中从 requestEventTime
到 scheduleUpdateOnFiber
这一流程,去创建更新任务,先我们详细看下更新任务是如何创建的。
前面的文章中我们讲到过,react 执行更新过程中,会将更新任务拆解,每一帧优先执行高优先级的任务,从而保证用户体验的流畅。那么即使对于同样优先级的任务,在任务多的情况下该优先执行哪一些呢?
react 通过 requestEventTime
方法去创建一个 currentEventTime,用于标识更新任务触发的时间,对于相同时间的任务,会批量去执行。同样优先级的任务,currentEventTime 值越小,就会越早执行。
我们看一下 requestEventTime
方法的实现:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsexport function requestEventTime() {if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {// 在 react 执行过程中,直接返回当前时间return now();}// 如果不在 react 执行过程中if (currentEventTime !== NoTimestamp) {// 正在执行浏览器事件,返回上次的 currentEventTimereturn currentEventTime;}// react 中断后首次更新,计算新的 currentEventTimecurrentEventTime = now();return currentEventTime;
}
在这个方法中,(executionContext & (RenderContext | CommitContext)
做了二进制运算,RenderContext
代表着 react 正在计算更新,CommitContext
代表着 react 正在提交更新。所以这句话是判断当前 react 是否处在计算或者提交更新的阶段,如果是则直接返回 now()
。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsexport const NoContext = /* */ 0b0000000;
const BatchedContext = /* */ 0b0000001;
const EventContext = /* */ 0b0000010;
const DiscreteEventContext = /* */ 0b0000100;
const LegacyUnbatchedContext = /* */ 0b0001000;
const RenderContext = /* */ 0b0010000;
const CommitContext = /* */ 0b0100000;
export const RetryAfterError = /* */ 0b1000000;let executionContext: ExecutionContext = NoContext;
再来看一下 now
的代码,这里的意思时,当前后的更新任务时间差小于 10ms 时,直接采用上次的 Scheduler_now
,这样可以抹平 10ms 内更新任务的时间差, 有利于批量更新:
// packages/react-reconciler/src/SchedulerWithReactIntegration.old.jsexport const now =initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;
综上所述,requestEvent
做的事情如下:
说完了相同优先级任务的触发时间,那么任务的优先级又是如何划分的呢?这里就要提到 requestUpdateLane
,我们来看一下其源码:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsexport function requestUpdateLane(fiber: Fiber): Lane {// ...// 根据记录下的事件的优先级,获取任务调度的优先级const schedulerPriority = getCurrentPriorityLevel();// ...let lane;if ((executionContext & DiscreteEventContext) !== NoContext &&schedulerPriority === UserBlockingSchedulerPriority) {// 如果是用户阻塞级别的事件,则通过InputDiscreteLanePriority 计算更新的优先级 lanelane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);} else {// 否则依据事件的优先级计算 schedulerLanePriorityconst schedulerLanePriority = schedulerPriorityToLanePriority(schedulerPriority,);if (decoupleUpdatePriorityFromScheduler) {const currentUpdateLanePriority = getCurrentUpdateLanePriority();// 根据计算得到的 schedulerLanePriority,计算更新的优先级 lanelane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);}return lane;
}
它首先找出会通过 getCurrentPriorityLevel
方法,根据 Scheduler 中记录的事件优先级,获取任务调度的优先级 schedulerPriority。然后通过 findUpdateLane
方法计算得出 lane,作为更新过程中的优先级。
findUpdateLane
这个方法中,按照事件的类型,匹配不同级别的 lane,事件类型的优先级划分如下,值越高,代表优先级越高:
// packages/react-reconciler/src/ReactFiberLane.jsexport const SyncLanePriority: LanePriority = 15;
export const SyncBatchedLanePriority: LanePriority = 14;
const InputDiscreteHydrationLanePriority: LanePriority = 13;
export const InputDiscreteLanePriority: LanePriority = 12;
const InputContinuousHydrationLanePriority: LanePriority = 11;
export const InputContinuousLanePriority: LanePriority = 10;
const DefaultHydrationLanePriority: LanePriority = 9;
export const DefaultLanePriority: LanePriority = 8;
const TransitionHydrationPriority: LanePriority = 7;
export const TransitionPriority: LanePriority = 6;
const RetryLanePriority: LanePriority = 5;
const SelectiveHydrationLanePriority: LanePriority = 4;
const IdleHydrationLanePriority: LanePriority = 3;
const IdleLanePriority: LanePriority = 2;
const OffscreenLanePriority: LanePriority = 1;
export const NoLanePriority: LanePriority = 0;
eventTime 和 lane 都创建好了之后,就该创建更新了,createUpdate
就是基于上面两个方法所创建的 eventTime 和 lane,去创建一个更新对象:
// packages/react-reconciler/src/ReactUpdateQueue.old.jsexport function createUpdate(eventTime: number, lane: Lane): Update<*> {const update: Update<*> = {eventTime, // 更新要出发的事件lane, // 优先级tag: UpdateState, // 指定更新的类型,0更新 1替换 2强制更新 3捕获性的更新payload: null, // 要更新的内容,例如 setState 接收的第一个参数callback: null, // 更新完成后的回调next: null, // 指向下一个更新};return update;
}
创建好了 update 对象之后,紧接着调用 enqueueUpdate
方法把update 对象放到 关联的 fiber 的 updateQueue 队列之中:
// packages/react-reconciler/src/ReactUpdateQueue.old.jsexport function enqueueUpdate(fiber: Fiber, update: Update) {// 获取当前 fiber 的更新队列const updateQueue = fiber.updateQueue;if (updateQueue === null) {// 若 updateQueue 为空,表示 fiber 还未渲染,直接退出return;}const sharedQueue: SharedQueue = (updateQueue: any).shared;const pending = sharedQueue.pending;if (pending === null) {// pending 为 null 时表示首次更新,创建循环列表update.next = update;} else {// 将 update 插入到循环列表中update.next = pending.next;pending.next = update;}sharedQueue.pending = update;// ...
}
上面的更新任务创建好了并且关联到了 fiber 上,下面就该到了 react render 阶段的核心之一 —— reconciler 阶段。
reconciler 阶段会协调任务去执行,以 scheduleUpdateOnFiber
为入口函数,首先会调用 checkForNestedUpdates
方法,检查嵌套的更新数量,若嵌套数量大于 50 层时,被认为是循环更新(无限更新)。此时会抛出异常,避免了例如在类组件 render 函数中调用了 setState 这种死循环的情况。
然后通过 markUpdateLaneFromFiberToRoot
方法,向上递归更新 fiber 的 lane,lane 的更新很简单,就是将当前任务 lane 与之前的 lane 进行二进制或运算叠加。
我们看一下其源码:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsexport function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane, eventTime: number,
) {// 检查是否有循环更新// 避免例如在类组件 render 函数中调用了 setState 这种死循环的情况checkForNestedUpdates();// ...// 自底向上更新 child.fiberLanesconst root = markUpdateLaneFromFiberToRoot(fiber, lane);// ...// 标记 root 有更新,将 update 的 lane 插入到root.pendingLanes 中markRootUpdated(root, lane, eventTime);if (lane === SyncLane) { // 同步任务,采用同步渲染if ((executionContext & LegacyUnbatchedContext) !== NoContext &&(executionContext & (RenderContext | CommitContext)) === NoContext) {// 如果本次是同步更新,并且当前还未开始渲染// 表示当前的 js 主线程空闲,并且没有 react 任务在执行// ...// 调用 performSyncWorkOnRoot 执行同步更新任务performSyncWorkOnRoot(root);} else {// 如果本次时同步更新,但是有 react 任务正在执行// 调用 ensureRootIsScheduled 去复用当前正在执行的任务,让其将本次的更新一并执行ensureRootIsScheduled(root, eventTime);schedulePendingInteractions(root, lane);// ...} else {// 如果本次更新是异步任务// ... // 调用 ensureRootIsScheduled 执行可中断更新ensureRootIsScheduled(root, eventTime);schedulePendingInteractions(root, lane);}mostRecentlyUpdatedRoot = root;
}
然后会根据任务类型以及当前线程所处的 react 执行阶段,去判断进行何种类型的更新:
当任务的类型为同步任务,并且当前的 js 主线程空闲(没有正在执行的 react 任务时),会通过 performSyncWorkOnRoot(root)
方法开始执行同步任务。
performSyncWorkOnRoot
里面主要做了两件事:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsfunction performSyncWorkOnRoot(root) {// ...exitStatus = renderRootSync(root, lanes);// ...commitRoot(root);// ...
}
当任务类型为同步类型,但是 js 主线程非空闲时。会执行 ensureRootIsScheduled
方法:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsfunction ensureRootIsScheduled(root: FiberRoot, currentTime: number) {// ...// 如果有正在执行的任务,if (existingCallbackNode !== null) {const existingCallbackPriority = root.callbackPriority;if (existingCallbackPriority === newCallbackPriority) {// 任务优先级没改变,说明可以复用之前的任务一起执行return;}// 任务优先级改变了,说明不能复用。// 取消正在执行的任务,重新去调度cancelCallback(existingCallbackNode);}// 进行一个新的调度let newCallbackNode;if (newCallbackPriority === SyncLanePriority) {// 如果是同步任务优先级,执行 performSyncWorkOnRootnewCallbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root),);} else if (newCallbackPriority === SyncBatchedLanePriority) {// 如果是批量同步任务优先级,执行 performSyncWorkOnRootnewCallbackNode = scheduleCallback(ImmediateSchedulerPriority,performSyncWorkOnRoot.bind(null, root),);} else {// ...// 如果不是批量同步任务优先级,执行 performConcurrentWorkOnRootnewCallbackNode = scheduleCallback(schedulerPriorityLevel,performConcurrentWorkOnRoot.bind(null, root),);}// ...
}
ensureRootIsScheduled
方法中,会先看加入了新的任务后根节点任务优先级是否有变更,如果无变更,说明新的任务会被当前的 schedule 一同执行;如果有变更,则创建新的 schedule,然后也是调用performSyncWorkOnRoot(root)
方法开始执行同步任务。
当任务的类型不是同步类型时,react 也会执行 ensureRootIsScheduled
方法,因为是异步任务,最终会执行 performConcurrentWorkOnRoot
方法,去进行可中断的更新,下面会详细讲到。
以同步更新为例,performSyncWorkOnRoot
会经过以下流程,performSyncWorkOnRoot
——> renderRootSync
——> workLoopSync
。
workLoopSync
中,只要 workInProgress(workInProgress fiber 树中新创建的 fiber 节点) 不为 null,就会一直循环,执行 performUnitOfWork
函数。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsfunction workLoopSync() {while (workInProgress !== null) {performUnitOfWork(workInProgress);}
}
可中断模式下,performConcurrentWorkOnRoot
会执行以下过程:performConcurrentWorkOnRoot
——> renderRootConcurrent
——> workLoopConcurrent
。
相比于 workLoopSync
, workLoopConcurrent
在每一次对 workInProgress 执行 performUnitOfWork
前,会先判断以下 shouldYield()
的值。若为 false 则继续执行,若为 true 则中断执行。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsfunction workLoopConcurrent() {while (workInProgress !== null && !shouldYield()) {performUnitOfWork(workInProgress);}
}
最终无论是同步执行任务,还是可中断地执行任务,都会进入 performUnitOfWork
函数中。
performUnitOfWork
中会以 fiber 作为单元,进行协调过程。每次 beginWork
执行后都会更新 workIngProgress,从而响应了上面 workLoop 的循环。
直至 fiber 树便利完成后,workInProgress 此时置为 null,执行 completeUnitOfWork
函数。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsfunction performUnitOfWork(unitOfWork: Fiber): void {// ...const current = unitOfWork.alternate;// ...let next;if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {// ...next = beginWork(current, unitOfWork, subtreeRenderLanes);} else {next = beginWork(current, unitOfWork, subtreeRenderLanes);}// ...if (next === null) {completeUnitOfWork(unitOfWork);} else {workInProgress = next;}ReactCurrentOwner.current = null;
}
beginWork
是根据当前执行环境,封装调用了 originalBeginWork
函数:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jslet beginWork;
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {beginWork = (current, unitOfWork, lanes) => {// ...try {return originalBeginWork(current, unitOfWork, lanes);} catch (originalError) {// ...}};
} else {beginWork = originalBeginWork;
}
originalBeginWork
中,会根据 workInProgress 的 tag 属性,执行不同类型的 react 元素的更新函数。但是他们都大同小异,不论是 tag 是何种类型,更新函数最终都会去调用 reconcileChildren
函数。
// packages/react-reconciler/src/ReactFiberBeginWork.old.jsfunction beginWork(current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes,
): Fiber | null {const updateLanes = workInProgress.lanes;workInProgress.lanes = NoLanes;// 针对 workInProgress 的tag,执行相应的更新switch (workInProgress.tag) {// ...case HostRoot:return updateHostRoot(current, workInProgress, renderLanes);case HostComponent:return updateHostComponent(current, workInProgress, renderLanes);// ...}// ...
}
以 updateHostRoot
为例,根据根 fiber 是否存在,去执行 mountChildFibers 或者 reconcileChildren:
// packages/react-reconciler/src/ReactFiberBeginWork.old.jsfunction updateHostRoot(current, workInProgress, renderLanes) {// ...const root: FiberRoot = workInProgress.stateNode;if (root.hydrate && enterHydrationState(workInProgress)) {// 若根 fiber 不存在,说明是首次渲染,调用 mountChildFibers// ...const child = mountChildFibers(workInProgress,null,nextChildren,renderLanes,);workInProgress.child = child;} else {// 若根 fiber 存在,调用 reconcileChildrenreconcileChildren(current, workInProgress, nextChildren, renderLanes);resetHydrationState();}return workInProgress.child;
}
reconcileChildren
做的事情就是 react 的另一核心之一 —— diff 过程,在下一篇文章中会详细讲。
当 workInProgress 为 null 时,也就是当前任务的 fiber 树遍历完之后,就进入到了 completeUnitOfWork
函数。
经过了 beginWork
操作,workInProgress 节点已经被打上了flags 副作用标签。completeUnitOfWork
方法中主要是逐层收集 effects
链,最终收集到 root 上,供接下来的commit阶段使用。
completeUnitOfWork
结束后,render 阶段便结束了,后面就到了 commit 阶段。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function completeUnitOfWork(unitOfWork: Fiber): void {let completedWork = unitOfWork;do {// ... // 对节点进行completeWork,生成DOM,更新props,绑定事件next = completeWork(current, completedWork, subtreeRenderLanes);if (returnFiber !== null &&(returnFiber.flags & Incomplete) === NoFlags) {// 将当前节点的 effectList 并入到父节点的 effectListif (returnFiber.firstEffect === null) {returnFiber.firstEffect = completedWork.firstEffect;}if (completedWork.lastEffect !== null) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = completedWork.firstEffect;}returnFiber.lastEffect = completedWork.lastEffect;}// 将自身添加到 effectList 链,添加时跳过 NoWork 和 PerformedWork的 flags,因为真正的 commit 时用不到const flags = completedWork.flags;if (flags > PerformedWork) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = completedWork;} else {returnFiber.firstEffect = completedWork;}returnFiber.lastEffect = completedWork;}}} while (completedWork !== null);// ...
}
刚刚上面说到了在执行可中断的更新时,浏览器会在每一帧空闲时刻去执行 react 更新任务,那么空闲时刻去执行是如何实现的呢?我们很容易联想到一个 api —— requestIdleCallback。但由于 requestIdleCallback 的兼容性问题以及 react 对应部分高优先级任务可能牺牲部分帧的需要,react 通过自己实现了类似的功能代替了 requestIdleCallback。
我们上面讲到执行可中断更新时,performConcurrentWorkOnRoot
函数时通过 scheduleCallback
包裹起来的:
scheduleCallback(schedulerPriorityLevel,performConcurrentWorkOnRoot.bind(null, root),
);
scheduleCallback
函数是引用了 packages/scheduler/src/Scheduler.js
路径下的 unstable_scheduleCallback
函数,我们来看一下这个函数,它会去按计划插入调度任务:
// packages/scheduler/src/Scheduler.jsfunction unstable_scheduleCallback(priorityLevel, callback, options) {// ...if (startTime > currentTime) {// 当前任务已超时,插入超时队列// ...} else {// 任务未超时,插入调度任务队列newTask.sortIndex = expirationTime;push(taskQueue, newTask);// 符合更新调度执行的标志if (!isHostCallbackScheduled && !isPerformingWork) {isHostCallbackScheduled = true;// requestHostCallback 调度任务requestHostCallback(flushWork);}}return newTask;
}
将任务插入了调度队列之后,会通过 requestHostCallback
函数去调度任务。
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.jsfunction 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 阶段的设计思想:
上一篇:Java抽象类
下一篇:apache 组件下载地址