react源码中的协调与调度
requestEventTime
其实在React
执行过程中,会有数不清的任务要去执行,但是他们会有一个优先级的判定
,假如两个事件的优先级一样
,那么React
是怎么去判定他们两谁先执行呢?
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
export function requestEventTime() {if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {// We're inside React, so it's fine to read the actual time.// react事件正在执行// executionContext// RenderContext 正在计算// CommitContext 正在提交// export 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;return now();}// 没有在react事件执行 NoTimestamp === -1if (currentEventTime !== NoTimestamp) { // 浏览器事件正在执行,返回上次的 currentEventTimereturn currentEventTime;}// 重新计算currentEventTime,当执行被中断后currentEventTime = now();return currentEventTime;
}
RenderContext
与CommitContext
表示正在计算更新和正在提交更新,返回now()
。- 如果是浏览器事件正在执行中,返回上一次的
currentEventTime
。 - 如果终止或者
中断react
任务执行的时候,则重新获取执行时间now(
)。 - 获取的时间
越小
,则执行的优先级越高
。
now()
并不是单纯的new Date()
,而是判定两次更新任务的时间是否小于10ms
,来决定是否复用
上一次的更新时间Scheduler_now
的。
export const now = initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;
其实各位猜想一下,对于10ms
级别的任务间隙时间,几乎是可以忽略不计的,那么这里就可以视为同样的任务,不需要有很大的性能开销,有利于批量更新
。
requestUpdateLane
requestEventTime位每一个需要执行的任务打上了触发更新时间标签,那么任务的优先级还需要进一步的确立,requestUpdateLane就是用来获取每一个任务执行的优先级的。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
export function requestUpdateLane(fiber: Fiber): Lane {// Special casesconst mode = fiber.mode;if ((mode & BlockingMode) === NoMode) {return (SyncLane: Lane);} else if ((mode & ConcurrentMode) === NoMode) {return getCurrentPriorityLevel() === ImmediateSchedulerPriority? (SyncLane: Lane): (SyncBatchedLane: Lane);} else if (!deferRenderPhaseUpdateToNextBatch &&(executionContext & RenderContext) !== NoContext &&workInProgressRootRenderLanes !== NoLanes) {// This is a render phase update. These are not officially supported. The// old behavior is to give this the same "thread" (expiration time) as// whatever is currently rendering. So if you call `setState` on a component// that happens later in the same render, it will flush. Ideally, we want to// remove the special case and treat them as if they came from an// interleaved event. Regardless, this pattern is not officially supported.// This behavior is only a fallback. The flag only exists until we can roll// out the setState warning, since existing code might accidentally rely on// the current behavior.return pickArbitraryLane(workInProgressRootRenderLanes);}// The algorithm for assigning an update to a lane should be stable for all// updates at the same priority within the same event. To do this, the inputs// to the algorithm must be the same. For example, we use the `renderLanes`// to avoid choosing a lane that is already in the middle of rendering.//// However, the "included" lanes could be mutated in between updates in the// same event, like if you perform an update inside `flushSync`. Or any other// code path that might call `prepareFreshStack`.//// The trick we use is to cache the first of each of these inputs within an// event. Then reset the cached values once we can be sure the event is over.// Our heuristic for that is whenever we enter a concurrent work loop.//// We'll do the same for `currentEventPendingLanes` below.if (currentEventWipLanes === NoLanes) {currentEventWipLanes = workInProgressRootIncludedLanes;}const isTransition = requestCurrentTransition() !== NoTransition;if (isTransition) {if (currentEventPendingLanes !== NoLanes) {currentEventPendingLanes =mostRecentlyUpdatedRoot !== null? mostRecentlyUpdatedRoot.pendingLanes: NoLanes;}return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);}// TODO: Remove this dependency on the Scheduler priority.// To do that, we're replacing it with an update lane priority.// 获取执行任务的优先级,便于调度const schedulerPriority = getCurrentPriorityLevel();// The old behavior was using the priority level of the Scheduler.// This couples React to the Scheduler internals, so we're replacing it// with the currentUpdateLanePriority above. As an example of how this// could be problematic, if we're not inside `Scheduler.runWithPriority`,// then we'll get the priority of the current running Scheduler task,// which is probably not what we want.let lane;if (// TODO: Temporary. We're removing the concept of discrete updates.(executionContext & DiscreteEventContext) !== NoContext &&// 用户block的类型事件schedulerPriority === UserBlockingSchedulerPriority) {// 通过findUpdateLane函数重新计算lanelane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);} else {// 根据优先级计算法则计算laneconst schedulerLanePriority = schedulerPriorityToLanePriority(schedulerPriority,);if (decoupleUpdatePriorityFromScheduler) {// In the new strategy, we will track the current update lane priority// inside React and use that priority to select a lane for this update.// For now, we're just logging when they're different so we can assess.const currentUpdateLanePriority = getCurrentUpdateLanePriority();if (schedulerLanePriority !== currentUpdateLanePriority &¤tUpdateLanePriority !== NoLanePriority) {if (__DEV__) {console.error('Expected current scheduler lane priority %s to match current update lane priority %s',schedulerLanePriority,currentUpdateLanePriority,);}}}// 根据计算得到的 schedulerLanePriority,计算更新的优先级 lanelane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);}return lane;
}
- 通过
getCurrentPriorityLevel
获得所有执行任务的调度优先级schedulerPriority
。 - 通过
findUpdateLane
计算lane
,作为更新中的优先级。
findUpdateLane
export function findUpdateLane(lanePriority: LanePriority, wipLanes: Lanes,
): Lane {switch (lanePriority) {case NoLanePriority:break;case SyncLanePriority:return SyncLane;case SyncBatchedLanePriority:return SyncBatchedLane;case InputDiscreteLanePriority: {const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);if (lane === NoLane) {// Shift to the next priority levelreturn findUpdateLane(InputContinuousLanePriority, wipLanes);}return lane;}case InputContinuousLanePriority: {const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);if (lane === NoLane) {// Shift to the next priority levelreturn findUpdateLane(DefaultLanePriority, wipLanes);}return lane;}case DefaultLanePriority: {let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);if (lane === NoLane) {// If all the default lanes are already being worked on, look for a// lane in the transition range.lane = pickArbitraryLane(TransitionLanes & ~wipLanes);if (lane === NoLane) {// All the transition lanes are taken, too. This should be very// rare, but as a last resort, pick a default lane. This will have// the effect of interrupting the current work-in-progress render.lane = pickArbitraryLane(DefaultLanes);}}return lane;}case TransitionPriority: // Should be handled by findTransitionLane insteadcase RetryLanePriority: // Should be handled by findRetryLane insteadbreak;case IdleLanePriority:let lane = pickArbitraryLane(IdleLanes & ~wipLanes);if (lane === NoLane) {lane = pickArbitraryLane(IdleLanes);}return lane;default:// The remaining priorities are not valid for updatesbreak;}invariant(false,'Invalid update priority: %s. This is a bug in React.',lanePriority,);
}
相关参考视频讲解:进入学习
lanePriority: LanePriority
export opaque type LanePriority =| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| 12| 13| 14| 15| 16| 17;
export opaque type Lanes = number;
export opaque type Lane = number;
export opaque type LaneMap<T> = Array<T>;import {ImmediatePriority as ImmediateSchedulerPriority,UserBlockingPriority as UserBlockingSchedulerPriority,NormalPriority as NormalSchedulerPriority,LowPriority as LowSchedulerPriority,IdlePriority as IdleSchedulerPriority,NoPriority as NoSchedulerPriority,
} from './SchedulerWithReactIntegration.new';// 同步任务
export 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;
createUpdate
export function createUpdate(eventTime: number, lane: Lane): Update<*> {const update: Update<*> = {eventTime, // 更新时间lane, // 优先级tag: UpdateState,//更新payload: null,// 需要更新的内容callback: null, // 更新完后的回调next: null, // 指向下一个更新};return update;
}
createUpdate
函数入参为eventTime
和lane
,输出一个update
对象,而对象中的tag
表示此对象要进行什么样的操作。
export const UpdateState = 0;// 更新
export const ReplaceState = 1;//替换
export const ForceUpdate = 2;//强制更新
export const CaptureUpdate = 3;//xx更新
createUpdate
就是单纯的给每一个任务进行包装,作为一个个体推入到更新队列中。
enqueueUpdate
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {// 获取当前更新队列?为啥呢?因为无法保证react是不是还有正在更新或者没有更新完毕的任务const updateQueue = fiber.updateQueue;// 如果更新队列为空,则表示fiber还未渲染,直接退出if (updateQueue === null) {// Only occurs if the fiber has been unmounted.return;}const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;const pending = sharedQueue.pending;if (pending === null) {// This is the first update. Create a circular list.// 还记得那个更新对象吗?update.next =>// 如果pedding位null,表示第一次渲染,那么他的指针为update本身update.next = update;} else {// 将update插入到更新队列循环当中update.next = pending.next;pending.next = update;}sharedQueue.pending = update;if (__DEV__) {if (currentlyProcessingQueue === sharedQueue &&!didWarnUpdateInsideUpdate) {console.error('An update (setState, replaceState, or forceUpdate) was scheduled ' +'from inside an update function. Update functions should be pure, ' +'with zero side-effects. Consider using componentDidUpdate or a ' +'callback.',);didWarnUpdateInsideUpdate = true;}}
}
- 这一步就是把需要更新的对象,与
fiber
更新队列关联起来。
总结
React
通过获取事件的优先级,处理具有同样优先级的事件,创建更新对象并与fiber
的更新队列关联起来。到这一步updateContainer
这个流程就走完了,也下面就是开始他的协调阶段
了。
协调与调度
协调
与调度
的流程大致如图所示:
reconciler流程
React
的reconciler
流程以scheduleUpdateOnFiber
为入口,并在checkForNestedUpdates
里面处理任务更新的嵌套层数,如果嵌套层数过大( >50
),就会认为是无效更新,则会抛出异常。之后便根据markUpdateLaneFromFiberToRoot
对当前的fiber
树,自底向上的递归fiber
的lane
,根据lane
做二进制比较或者位运算处理。详情如下:
- 如果当前执行任务的优先级为同步,则去判断有无正在执行的
React
任务。如果没有则执行ensureRootIsScheduled
,进行调度处理。 - 如果当前任务优先级是异步执行,则执行
ensureRootIsScheduled
进行调度处理。
export function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane, eventTime: number,
) {// 检查嵌套层数,避免是循环做无效操作checkForNestedUpdates();warnAboutRenderPhaseUpdatesInDEV(fiber);// 更新当前更新队列里面的任务优先级,自底而上更新child.fiberLanesconst root = markUpdateLaneFromFiberToRoot(fiber, lane);if (root === null) {warnAboutUpdateOnUnmountedFiberInDEV(fiber);return null;}// Mark that the root has a pending update.// 标记root有更新的,执行它markRootUpdated(root, lane, eventTime);if (root === workInProgressRoot) {// Received an update to a tree that's in the middle of rendering. Mark// that there was an interleaved update work on this root. Unless the// `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render// phase update. In that case, we don't treat render phase updates as if// they were interleaved, for backwards compat reasons.if (deferRenderPhaseUpdateToNextBatch ||(executionContext & RenderContext) === NoContext) {workInProgressRootUpdatedLanes = mergeLanes(workInProgressRootUpdatedLanes,lane,);}if (workInProgressRootExitStatus === RootSuspendedWithDelay) {// The root already suspended with a delay, which means this render// definitely won't finish. Since we have a new update, let's mark it as// suspended now, right before marking the incoming update. This has the// effect of interrupting the current render and switching to the update.// TODO: Make sure this doesn't override pings that happen while we've// already started rendering.markRootSuspended(root, workInProgressRootRenderLanes);}}// TODO: requestUpdateLanePriority also reads the priority. Pass the// priority as an argument to that function and this one.// 获取当前优先级层次const priorityLevel = getCurrentPriorityLevel();// 同步任务,采用同步更新的方式if (lane === SyncLane) {if (// Check if we're inside unbatchedUpdates(executionContext & LegacyUnbatchedContext) !== NoContext &&// Check if we're not already rendering(executionContext & (RenderContext | CommitContext)) === NoContext) {// Register pending interactions on the root to avoid losing traced interaction data.// 同步而且没有react任务在执行,调用performSyncWorkOnRootschedulePendingInteractions(root, lane);// This is a legacy edge case. The initial mount of a ReactDOM.render-ed// root inside of batchedUpdates should be synchronous, but layout updates// should be deferred until the end of the batch.performSyncWorkOnRoot(root);} else {// 如果有正在执行的react任务,那么执行它ensureRootIsScheduled去复用当前正在执行的任务// 跟本次更新一起进行ensureRootIsScheduled(root, eventTime);schedulePendingInteractions(root, lane);if (executionContext === NoContext) {// Flush the synchronous work now, unless we're already working or inside// a batch. This is intentionally inside scheduleUpdateOnFiber instead of// scheduleCallbackForFiber to preserve the ability to schedule a callback// without immediately flushing it. We only do this for user-initiated// updates, to preserve historical behavior of legacy mode.resetRenderTimer();flushSyncCallbackQueue();}}} else {// Schedule a discrete update but only if it's not Sync.// 如果此次是异步任务if ((executionContext & DiscreteEventContext) !== NoContext &&// Only updates at user-blocking priority or greater are considered// discrete, even inside a discrete event.(priorityLevel === UserBlockingSchedulerPriority ||priorityLevel === ImmediateSchedulerPriority)) {// This is the result of a discrete event. Track the lowest priority// discrete update per root so we can flush them early, if needed.if (rootsWithPendingDiscreteUpdates === null) {rootsWithPendingDiscreteUpdates = new Set([root]);} else {rootsWithPendingDiscreteUpdates.add(root);}}// Schedule other updates after in case the callback is sync.// 可以中断更新,只要调用ensureRootIsScheduled => performConcurrentWorkOnRootensureRootIsScheduled(root, eventTime);schedulePendingInteractions(root, lane);}// We use this when assigning a lane for a transition inside// `requestUpdateLane`. We assume it's the same as the root being updated,// since in the common case of a single root app it probably is. If it's not// the same root, then it's not a huge deal, we just might batch more stuff// together more than necessary.mostRecentlyUpdatedRoot = root;
}
同步任务类型执行机制
当任务的类型为同步任务,并且当前的js
主线程空闲,会通过 performSyncWorkOnRoot(root)
方法开始执行同步任务。
performSyncWorkOnRoot
里面主要做了两件事:
renderRootSync
从根节点开始进行同步渲染任务commitRoot
执行commit
流程
当前js
线程中有正在执行的任务时候,就会触发ensureRootIsScheduled
函数。 ensureRootIsScheduled
里面主要是处理当前加入的更新任务的lane
是否有变化:
- 如果没有变化则表示跟当前的
schedule
一起执行。 - 如果有则创建新的
schedule
。 - 调用
performSyncWorkOnRoot
执行同步任务。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {const existingCallbackNode = root.callbackNode;// Check if any lanes are being starved by other work. If so, mark them as// expired so we know to work on those next.markStarvedLanesAsExpired(root, currentTime);// Determine the next lanes to work on, and their priority.const nextLanes = getNextLanes(root,root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,);// This returns the priority level computed during the `getNextLanes` call.const newCallbackPriority = returnNextLanesPriority();if (nextLanes === NoLanes) {// Special case: There's nothing to work on.if (existingCallbackNode !== null) {cancelCallback(existingCallbackNode);root.callbackNode = null;root.callbackPriority = NoLanePriority;}return;}// Check if there's an existing task. We may be able to reuse it.if (existingCallbackNode !== null) {const existingCallbackPriority = root.callbackPriority;if (existingCallbackPriority === newCallbackPriority) {// The priority hasn't changed. We can reuse the existing task. Exit.return;}// The priority changed. Cancel the existing callback. We'll schedule a new// one below.cancelCallback(existingCallbackNode);}// Schedule a new callback.let newCallbackNode;if (newCallbackPriority === SyncLanePriority) {// Special case: Sync React callbacks are scheduled on a special// internal queue// 同步任务调用performSyncWorkOnRootnewCallbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root),);} else if (newCallbackPriority === SyncBatchedLanePriority) {newCallbackNode = scheduleCallback(ImmediateSchedulerPriority,performSyncWorkOnRoot.bind(null, root),);} else {// 异步任务调用 performConcurrentWorkOnRootconst schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority,);newCallbackNode = scheduleCallback(schedulerPriorityLevel,performConcurrentWorkOnRoot.bind(null, root),);}root.callbackPriority = newCallbackPriority;root.callbackNode = newCallbackNode;
}
所以任务类型为同步的时候,不管js
线程空闲与否,都会走到performSyncWorkOnRoot
,进而走renderRootSync
、workLoopSync
流程,而在workLoopSync
中,只要workInProgress fiber
不为null
,则会一直循环执行performUnitOfWork
,而performUnitOfWork
中会去执行beginWork
和completeWork
,也就是上一章里面说的beginWork
流程去创建每一个fiber
节点
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsfunction workLoopSync() {while (workInProgress !== null) {performUnitOfWork(workInProgress);}
}
异步任务类型执行机制
异步任务则会去执行performConcurrentWorkOnRoot
,进而去执行renderRootConcurrent
、workLoopConcurrent
,但是与同步任务不同的是异步任务是可以中断的,这个可中断的关键字就在于shouldYield
,它本身返回值是一个false
,为true
则可以中断。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsfunction workLoopConcurrent() {while (workInProgress !== null && !shouldYield()) {performUnitOfWork(workInProgress);}
}
每一次在执行performUnitOfWork
之前都会关注一下shouldYield()
返回值,也就是说的reconciler
过程可中断的意思。
shouldYield
// packages\scheduler\src\SchedulerPostTask.js
export function unstable_shouldYield() {return getCurrentTime() >= deadline;
}
getCurrentTime
为new Date()
,deadline
为浏览器处理每一帧结束时间戳
,所以这里表示的是,在浏览器每一帧空闲的时候,才会去处理此任务,如果当前任务在浏览器执行的某一帧
里面,则会中断当前任务
,等待浏览器当前帧执行完毕,等到下一帧空闲
的时候,才会去执行当前任务。
所以不管在workLoopConcurrent
还是workLoopSync
中,都会根据当前的workInProgress fiber
是否为null
来进行循环调用performUnitOfWork
。根据流程图以及上面说的这一些,可以看得出来从beginWork
到completeUnitOfWork
这个过程究竟干了什么。
这三章将会讲解fiber
树的reconcileChildren
过程、completeWork
过程、commitMutationEffects
…insertOrAppendPlacementNodeIntoContainer(DOM)
过程。这里将详细解读v17
版本的React
的diff算法
、虚拟dom到真实dom的创建
,函数生命钩子
的执行流程等。
performUnitOfWork
function performUnitOfWork(unitOfWork: Fiber): void {// The current, flushed, state of this fiber is the alternate. Ideally// nothing should rely on this, but relying on it here means that we don't// need an additional field on the work in progress.const current = unitOfWork.alternate;setCurrentDebugFiberInDEV(unitOfWork);let next;if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {startProfilerTimer(unitOfWork);next = beginWork(current, unitOfWork, subtreeRenderLanes);stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);} else {// beginWorknext = beginWork(current, unitOfWork, subtreeRenderLanes);}resetCurrentDebugFiberInDEV();unitOfWork.memoizedProps = unitOfWork.pendingProps;if (next === null) {// If this doesn't spawn new work, complete the current work.// completeUnitOfWorkcompleteUnitOfWork(unitOfWork);} else {workInProgress = next;}ReactCurrentOwner.current = null;
}
所以在performUnitOfWork
里面,每一次执行beginWork
,进行workIngProgress更新,当遍历完毕整棵fiber树之后便会执行completeUnitOfWork
。
beginWork
我们可以看到beginWork
就是originBeginWork
得实际执行。我们翻开beginWork
的源码可以看到,它便是根据不同的workInProgress.tag
执行不同组件类型的处理函数,这里就不去拆分的太细,只有有想法便会单独出一篇文章讲述这个的细节,但是最后都会去调用reconcileChildren
。
completeUnitOfWork
当遍历完毕执行beginWork
,遍历完毕之后就会走completeUnitOfWork
。
function completeUnitOfWork(unitOfWork: Fiber): void {// Attempt to complete the current unit of work, then move to the next// sibling. If there are no more siblings, return to the parent fiber.let completedWork = unitOfWork;do {// The current, flushed, state of this fiber is the alternate. Ideally// nothing should rely on this, but relying on it here means that we don't// need an additional field on the work in progress.const current = completedWork.alternate;const returnFiber = completedWork.return;// Check if the work completed or if something threw.if ((completedWork.flags & Incomplete) === NoFlags) {setCurrentDebugFiberInDEV(completedWork);let next;if (!enableProfilerTimer ||(completedWork.mode & ProfileMode) === NoMode) {// 绑定事件,更新props,更新domnext = completeWork(current, completedWork, subtreeRenderLanes);} else {startProfilerTimer(completedWork);next = completeWork(current, completedWork, subtreeRenderLanes);// Update render duration assuming we didn't error.stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);}resetCurrentDebugFiberInDEV();if (next !== null) {// Completing this fiber spawned new work. Work on that next.workInProgress = next;return;}resetChildLanes(completedWork);if (returnFiber !== null &&// Do not append effects to parents if a sibling failed to complete(returnFiber.flags & Incomplete) === NoFlags) {// Append all the effects of the subtree and this fiber onto the effect// list of the parent. The completion order of the children affects the// side-effect order.// 把已收集到的副作用,合并到父级effect lists中if (returnFiber.firstEffect === null) {returnFiber.firstEffect = completedWork.firstEffect;}if (completedWork.lastEffect !== null) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = completedWork.firstEffect;}returnFiber.lastEffect = completedWork.lastEffect;}// If this fiber had side-effects, we append it AFTER the children's// side-effects. We can perform certain side-effects earlier if needed,// by doing multiple passes over the effect list. We don't want to// schedule our own side-effect on our own list because if end up// reusing children we'll schedule this effect onto itself since we're// at the end.const flags = completedWork.flags;// Skip both NoWork and PerformedWork tags when creating the effect// list. PerformedWork effect is read by React DevTools but shouldn't be// committed.// 跳过NoWork,PerformedWork在commit阶段用不到if (flags > PerformedWork) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = completedWork;} else {returnFiber.firstEffect = completedWork;}returnFiber.lastEffect = completedWork;}}} else {// This fiber did not complete because something threw. Pop values off// the stack without entering the complete phase. If this is a boundary,// capture values if possible.const next = unwindWork(completedWork, subtreeRenderLanes);// Because this fiber did not complete, don't reset its expiration time.if (next !== null) {// If completing this work spawned new work, do that next. We'll come// back here again.// Since we're restarting, remove anything that is not a host effect// from the effect tag.next.flags &= HostEffectMask;workInProgress = next;return;}if (enableProfilerTimer &&(completedWork.mode & ProfileMode) !== NoMode) {// Record the render duration for the fiber that errored.stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);// Include the time spent working on failed children before continuing.let actualDuration = completedWork.actualDuration;let child = completedWork.child;while (child !== null) {actualDuration += child.actualDuration;child = child.sibling;}completedWork.actualDuration = actualDuration;}if (returnFiber !== null) {// Mark the parent fiber as incomplete and clear its effect list.returnFiber.firstEffect = returnFiber.lastEffect = null;returnFiber.flags |= Incomplete;}}// 兄弟层指针const siblingFiber = completedWork.sibling;if (siblingFiber !== null) {// If there is more work to do in this returnFiber, do that next.workInProgress = siblingFiber;return;}// Otherwise, return to the parentcompletedWork = returnFiber;// Update the next thing we're working on in case something throws.workInProgress = completedWork;} while (completedWork !== null);// We've reached the root.if (workInProgressRootExitStatus === RootIncomplete) {workInProgressRootExitStatus = RootCompleted;}
}
他的作用便是逐层收集fiber
树上已经被打上的副作用标签flags
,一直收集到root
上面以便于在commit
阶段进行dom
的增删改
。
scheduler流程
在这里应该有很多人不明白,协调
和调度
是什么意思,通俗来讲:
- 协调就是协同合作
- 调度就是执行命令
所以在React
中协调就是一个js
线程中,需要安排很多模块去完成整个流程,例如:同步异步lane
的处理,reconcileChildren
处理fiber
节点等,保证整个流程有条不紊的执行。调度表现为让空闲的js线程
(帧层面)去执行其他任务,这个过程称之为调度,那么它到底是怎么去做的呢?
我们回到处理异步任务那里,我们会发现performConcurrentWorkOnRoot
这个函数外面包裹了一层scheduleCallback
:
newCallbackNode = scheduleCallback(schedulerPriorityLevel,performConcurrentWorkOnRoot.bind(null, root),
)
export function scheduleCallback(reactPriorityLevel: ReactPriorityLevel, callback: SchedulerCallback, options: SchedulerCallbackOptions | void | null,
) {const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);return Scheduler_scheduleCallback(priorityLevel, callback, options);
}
我们几经周折找到了声明函数的地方
// packages/scheduler/src/Scheduler.js
function unstable_scheduleCallback(priorityLevel, callback, options) {var currentTime = getCurrentTime();var startTime;if (typeof options === 'object' && options !== null) {var delay = options.delay;if (typeof delay === 'number' && delay > 0) {startTime = currentTime + delay;} else {startTime = currentTime;}} else {startTime = currentTime;}var timeout;switch (priorityLevel) {case ImmediatePriority:timeout = IMMEDIATE_PRIORITY_TIMEOUT;break;case UserBlockingPriority:timeout = USER_BLOCKING_PRIORITY_TIMEOUT;break;case IdlePriority:timeout = IDLE_PRIORITY_TIMEOUT;break;case LowPriority:timeout = LOW_PRIORITY_TIMEOUT;break;case NormalPriority:default:timeout = NORMAL_PRIORITY_TIMEOUT;break;}var expirationTime = startTime + timeout;var newTask = {id: taskIdCounter++,callback,priorityLevel,startTime,expirationTime,sortIndex: -1,};if (enableProfiling) {newTask.isQueued = false;}if (startTime > currentTime) {// This is a delayed task.newTask.sortIndex = startTime;push(timerQueue, newTask);if (peek(taskQueue) === null && newTask === peek(timerQueue)) {// All tasks are delayed, and this is the task with the earliest delay.if (isHostTimeoutScheduled) {// Cancel an existing timeout.cancelHostTimeout();} else {isHostTimeoutScheduled = true;}// Schedule a timeout.requestHostTimeout(handleTimeout, startTime - currentTime);}} else {newTask.sortIndex = expirationTime;push(taskQueue, newTask);if (enableProfiling) {markTaskStart(newTask, currentTime);newTask.isQueued = true;}// Schedule a host callback, if needed. If we're already performing work,// wait until the next time we yield.if (!isHostCallbackScheduled && !isPerformingWork) {isHostCallbackScheduled = true;requestHostCallback(flushWork);}}return newTask;
}
- 当
starttime > currentTime
的时候,表示任务超时,插入超时队列。 - 任务没有超时,插入调度队列
- 执行
requestHostCallback
调度任务。
// 创建消息通道const channel = new MessageChannel();const port = channel.port2;channel.port1.onmessage = performWorkUntilDeadline;// 告知scheduler开始调度requestHostCallback = function(callback) {scheduledHostCallback = callback;if (!isMessageLoopRunning) {isMessageLoopRunning = true;port.postMessage(null);}};
react
通过 new MessageChannel()
创建了消息通道,当发现js
线程空闲时,通过postMessage
通知 scheduler
开始调度。performWorkUntilDeadline
函数功能为处理react
调度开始时间更新到结束时间。
这里我们要关注一下设备帧速率。
forceFrameRate = function(fps) {if (fps < 0 || fps > 125) {// Using console['error'] to evade Babel and ESLintconsole['error']('forceFrameRate takes a positive int between 0 and 125, ' +'forcing frame rates higher than 125 fps is not supported',);return;}if (fps > 0) {yieldInterval = Math.floor(1000 / fps);} else {// reset the framerateyieldInterval = 5;}};
performWorkUntilDeadline
const performWorkUntilDeadline = () => {if (scheduledHostCallback !== null) {const currentTime = getCurrentTime();// Yield after `yieldInterval` ms, regardless of where we are in the vsync// cycle. This means there's always time remaining at the beginning of// the message event.// 更新当前帧结束时间deadline = currentTime + yieldInterval;const hasTimeRemaining = true;try {const hasMoreWork = scheduledHostCallback(hasTimeRemaining,currentTime,);// 还有任务就继续执行if (!hasMoreWork) {isMessageLoopRunning = false;scheduledHostCallback = null;} else {// If there's more work, schedule the next message event at the end// of the preceding one.// 没有就postMessageport.postMessage(null);}} catch (error) {// If a scheduler task throws, exit the current browser task so the// error can be observed.port.postMessage(null);throw error;}} else {isMessageLoopRunning = false;}// Yielding to the browser will give it a chance to paint, so we can// reset this.needsPaint = false;};
总结
本文讲了React
在状态改变的时候,会根据当前任务优先级,等一些列操作去创建workInProgress fiber
链表树,在协调阶段,会根据浏览器每一帧去做比较,假如浏览器每一帧
执行时间戳高于当前时间,则表示当前帧没有空闲时间,当前任务则必须要等到下一个空闲帧
才能去执行的可中断
的策略。还有关于beginWork
的遍历执行更新fiber
的节点。那么到这里这一章就讲述完毕了,下一章讲一讲React的diff算法
相关文章:

react源码中的协调与调度
requestEventTime 其实在React执行过程中,会有数不清的任务要去执行,但是他们会有一个优先级的判定,假如两个事件的优先级一样,那么React是怎么去判定他们两谁先执行呢? // packages/react-reconciler/src/ReactFibe…...

如何快速、全面、深入地掌握一门编程语言
思考路线 如何快速? 什么样的Demo才能让人觉得你掌握了它? 空 判断:构造一个可以判断所有空的 is_empty 函数 for 循环:i 和 集合迭代两种 时间获取:年/月/日 时分秒 时间戳与时间格式互转 休眠时间函数 字符串处理…...

python五子棋代码最简单的,python五子棋代码画棋盘
大家好,本文将围绕python五子棋代码输赢逻辑判断展开说明,如何用python制作五子棋游戏是一个很多人都想弄明白的事情,想搞清楚python五子棋代码最简单的需要先了解以下几个事情。 1、求解用python 编写五子棋怎样编写判断输赢的函数ÿ…...

C++ 智能指针的原理:auto_ptr、unique_ptr、shared_ptr、weak_ptr
目录一、理解智能指针1.普通指针的使用二、智能指针1.auto_ptr2.unique_ptr3.shared_ptr(1)了解shared_ptr(2)shared_ptr的缺陷4.weak_ptr本文代码在win10的vs2019中通过编译。 一、理解智能指针 1.普通指针的使用 如果程序需要…...
二叉树前中后层次遍历,递归实现
文章目录前序遍历代码\Python代码\C中序遍历代码\Python代码\C后序遍历代码\Python代码\C层序遍历代码\Python代码\C反向层序遍历代码\Python代码\C总结前序遍历 题目链接 前序遍历意思就是按照“根节点-左子树-右子树”的顺序来遍历二叉树,通过递归方法来实现…...

【RA4M2系列开发板GPIO体验2按键控制LED】
【RA4M2系列开发板GPIO体验2按键控制LED】1. 前言2. 配置工程2.1 新建FSP项目2.2 硬件连接以及FSP配置2.2.1 硬件连接2.2.2 FSP配置3. 软件实现3.1 实现的功能3.2 FreeRTOS使用3.2.1 Stack分配函数3.2.2 LED任务3.2.3 Key任务3.3 程序设计3.3.1 设置输出hex文件3.3.2 编译3.3.3…...

初步介绍CUDA中的统一内存
初步介绍CUDA中的统一内存 更多精彩内容: https://www.nvidia.cn/gtc-global/?ncidref-dev-876561 文章目录初步介绍CUDA中的统一内存为此,我向您介绍了统一内存,它可以非常轻松地分配和访问可由系统中任何处理器、CPU 或 GPU 上运行的代码使用的数据。…...

UVM实战--加法器
前言 这里以UVM实战(张强)第二章为基础修改原有的DUT,将DUT修改为加法器,从而修改代码以使得更加深入的了解各个组件的类型和使用。 一. 组件的基本框架 和第二章的平台的主要区别点 (1)有两个transactio…...

Linux系统点亮LED
目录应用层操控硬件的两种方式sysfs 文件系统sysfs 与/sys总结标准接口与非标准接口LED 硬件控制方式编写LED 应用程序在开发板上测试对于一款学习型开发板来说,永远都绕不开LED 这个小小的设备,基本上每块板子都至少会有一颗 LED 小灯,对于我…...

在superset中快速制作报表或仪表盘
在中小型企业,当下需要快速迭代、快速了解运营效果的业务,急需一款开源、好用、能快速迭代生产的报表系统。 老板很关心,BI工程师很关心,同时系统开发人员也同样关心,一个好的技术选型往往能够帮助公司减少很多成本&a…...

【可视化实战】Python 绘制出来的数据大屏真的太惊艳了
今天我们在进行一个Python数据可视化的实战练习,用到的模块叫做Panel,我们通过调用此模块来绘制动态可交互的图表以及数据大屏的制作。 而本地需要用到的数据集,可在kaggle上面获取 https://www.kaggle.com/datasets/rtatman/188-million-us…...

Obsidium一键编码作业,Obsidia惊人属性
Obsidium一键编码作业,Obsidia惊人属性 每个区域都包含几个可定制的功能,允许用户确定如何完全执行应用程序的安全性。Obsidia的功能区允许用户存储任何调整或一键编码作业。 Obsidia惊人属性: 代码虚拟化:代码虚拟化允许您转换程序代码的特定…...

约束优化:约束优化的三种序列无约束优化方法
文章目录约束优化:约束优化的三种序列无约束优化方法外点罚函数法L2-罚函数法:非精确算法对于等式约束对于不等式约束L1-罚函数法:精确算法内点罚函数法:障碍函数法等式约束优化问题的拉格朗日函数法:Uzawas Method fo…...

RocketMQ快速入门:消息发送、延迟消息、消费重试
一起学编程,让生活更随和! 如果你觉得是个同道中人,欢迎关注博主gzh:【随和的皮蛋桑】。 专注于Java基础、进阶、面试以及计算机基础知识分享🐳。偶尔认知思考、日常水文🐌。 目录1、RocketMQ消息结构1.1…...

FANUC机器人通过KAREL程序实现与PLC位置坐标通信的具体方法示例
FANUC机器人通过KAREL程序实现与PLC位置坐标通信的具体方法示例 在通信IO点位数量足够的情况下,可以使用机器人的IO点传输位置数据,这里以传输机器人的实时位置为例进行说明。 基本流程如下图所示: 基本步骤可参考如下: 首先确认机器人控制柜已经安装了总线通信软件(例如…...

[蓝桥杯 2015 省 B] 移动距离
蓝桥杯 2015 年省赛 B 组 H 题题目描述X 星球居民小区的楼房全是一样的,并且按矩阵样式排列。其楼房的编号为 1,2,3,⋯ 。当排满一行时,从下一行相邻的楼往反方向排号。比如:当小区排号宽度为 6 时,开始情形如下:我们的…...

Pandas库入门仅需10分钟
数据处理的时候经常性需要整理出表格,在这里介绍pandas常见使用,目录如下: 数据结构导入导出文件对数据进行操作 – 增加数据(创建数据) – 删除数据 – 改动数据 – 查找数据 – 常用操作(转置࿰…...
python的socket通信中,如何设置可以让两台主机通过外网访问?
要让两台主机通过外网进行Socket通信,需要在网络设置和代码实现两个方面进行相应的配置。下面是具体的步骤: 确认网络环境:首先要确保两台主机都能够通过外网访问。可以通过ping命令或者telnet命令来测试两台主机之间是否可以互相访问。 确定…...
检测数据的方法(回顾)
检测数据类型的4种方法typeofinstanceofconstructor{}.toString.call() 检测数据类型的4种方法 typeof 定义 用来检测数据类型的运算符 返回一个字符串,表示操作值的数据类型(7种) number,string,boolean,object,u…...

比特数据结构与算法(第三章_上)栈的概念和实现(力扣:20. 有效的括号)
一、栈(stack)栈的概念:① 栈是一种特殊的线性表,它只允许在固定的一端进行插入和删除元素的操作。② 进行数据插入的删除和操作的一端,称为栈顶 。另一端则称为 栈底 。③ 栈中的元素遵守后进先出的原则,即…...

矩阵QR分解
1 orthonormal 向量与 Orthogonal 矩阵 orthonormal 向量定义为 ,任意向量 相互垂直,且模长为1; 如果将 orthonormal 向量按列组织成矩阵,矩阵为 Orthogonal 矩阵,满足如下性质: ; 当为方阵时&…...
Java集合初始化:Lists.newArrayList vs new ArrayList()
文章目录 前言一、核心区别全景图二、代码实现深度对比1. 初始化方式对比2. 容量预分配机制 三、性能与底层原理1. 内存分配策略2. 基准测试数据(JMH) 四、Guava的进阶功能生态1. 集合转换2. 集合分片3. 不可变集合创建 五、最佳实践指南六、源码级实现解…...

1panel面板中部署SpringBoot和Vue前后端分离系统 【图文教程】
1panel面板中部署SpringBoot和Vue前后端分离系统 一,1panel面板部署二,安装OpenResty三,安装MySQL,Redis等Spring boot 运行依赖环境四,SpringBoot 应用配置及打包部署配置打包部署 五 ,前端VUE应用配置打包…...

基于cornerstone3D的dicom影像浏览器 第二十九章 自定义菜单组件
文章目录 前言一、程序结构1. 菜单数据结构2. XMenu.vue3. XSubMenu.vue4. XSubMenuSlot.vue5. XMenuItem.vue 二、调用流程总结 前言 菜单用于组织程序功能,为用户提供导航。是用户与程序交互非常重要的接口。 开源组件库像Element Plus和Ant Design中都提供了功能…...
#开发环境篇:postMan可以正常调通,但是浏览器里面一直报403
本地header代理下面内容即可 headers: { // 添加必要的请求头 ‘Host’: ‘服务端域名’, ‘Origin’: https://服务端域名, ‘Referer’: https://服务端域名 }, devServer: {// 本地开发代理API地址proxy: {^/file: {target: https://服务端域名,changeOrigin: true, // 是否…...

移动端测试岗位高频面试题及解析
文章目录 一、基础概念二、自动化测试三、性能测试四、专项测试五、安全与稳定性六、高级场景七、实战难题八、其他面题 一、基础概念 移动端测试与Web测试的核心区别? 解析:网络波动(弱网测试)、设备碎片化(机型适配&…...
【Prompt实战】国际翻译小组
本文原创作者:姚瑞南 AI-agent 大模型运营专家/音乐人/野生穿搭model,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。&#…...

飞云智能波段主图+多空短线决策副图指标,组合操盘技术图文解说
如上图,组合指标:主图-飞云智能波段,红线上红色K线标记,波段做多.副图指标-多空短线决策,跟踪做短线,红柱做多,绿柱短线卖出或做空。 实战操作中,我们在主图红色线支撑上红色K线出现…...
Delphi 实现远程连接 Access 数据库的指南
方法一:通过局域网共享 Access 文件(简单但有限) 步骤 1:共享 Access 数据库 将 .mdb 或 .accdb 文件放在局域网内某台电脑的共享文件夹中。 右键文件夹 → 属性 → 共享 → 启用共享并设置权限(需允许网络用户读写&a…...

打造高效多模态RAG系统:原理与评测方法详解
引言 随着信息检索与生成式AI的深度融合,检索增强生成(RAG, Retrieval-Augmented Generation) 已成为AI领域的重要技术方向。传统RAG系统主要依赖文本数据,但真实世界中的信息往往包含图像、表格等多模态内容。多模态RAG…...