当前位置: 首页 > news >正文

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;
}
  • RenderContextCommitContext表示正在计算更新和正在提交更新,返回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 &&currentUpdateLanePriority !== 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函数入参为eventTimelane,输出一个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流程

Reactreconciler流程以scheduleUpdateOnFiber为入口,并在checkForNestedUpdates里面处理任务更新的嵌套层数,如果嵌套层数过大( >50 ),就会认为是无效更新,则会抛出异常。之后便根据markUpdateLaneFromFiberToRoot对当前的fiber树,自底向上的递归fiberlane,根据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,进而走renderRootSyncworkLoopSync流程,而在workLoopSync中,只要workInProgress fiber不为null,则会一直循环执行performUnitOfWork,而performUnitOfWork中会去执行beginWorkcompleteWork,也就是上一章里面说的beginWork流程去创建每一个fiber节点

// packages/react-reconciler/src/ReactFiberWorkLoop.old.jsfunction workLoopSync() {while (workInProgress !== null) {performUnitOfWork(workInProgress);}
}

异步任务类型执行机制

异步任务则会去执行performConcurrentWorkOnRoot,进而去执行renderRootConcurrentworkLoopConcurrent,但是与同步任务不同的是异步任务是可以中断的,这个可中断的关键字就在于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;
}

getCurrentTimenew Date()deadline为浏览器处理每一帧结束时间戳,所以这里表示的是,在浏览器每一帧空闲的时候,才会去处理此任务,如果当前任务在浏览器执行的某一帧里面,则会中断当前任务,等待浏览器当前帧执行完毕,等到下一帧空闲的时候,才会去执行当前任务。

所以不管在workLoopConcurrent还是workLoopSync中,都会根据当前的workInProgress fiber是否为null来进行循环调用performUnitOfWork。根据流程图以及上面说的这一些,可以看得出来从beginWorkcompleteUnitOfWork这个过程究竟干了什么。

这三章将会讲解fiber树的reconcileChildren过程、completeWork过程、commitMutationEffectsinsertOrAppendPlacementNodeIntoContainer(DOM)过程。这里将详细解读v17版本的Reactdiff算法虚拟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执行过程中&#xff0c;会有数不清的任务要去执行&#xff0c;但是他们会有一个优先级的判定&#xff0c;假如两个事件的优先级一样&#xff0c;那么React是怎么去判定他们两谁先执行呢&#xff1f; // packages/react-reconciler/src/ReactFibe…...

如何快速、全面、深入地掌握一门编程语言

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

python五子棋代码最简单的,python五子棋代码画棋盘

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

C++ 智能指针的原理:auto_ptr、unique_ptr、shared_ptr、weak_ptr

目录一、理解智能指针1.普通指针的使用二、智能指针1.auto_ptr2.unique_ptr3.shared_ptr&#xff08;1&#xff09;了解shared_ptr&#xff08;2&#xff09;shared_ptr的缺陷4.weak_ptr本文代码在win10的vs2019中通过编译。 一、理解智能指针 1.普通指针的使用 如果程序需要…...

二叉树前中后层次遍历,递归实现

文章目录前序遍历代码\Python代码\C中序遍历代码\Python代码\C后序遍历代码\Python代码\C层序遍历代码\Python代码\C反向层序遍历代码\Python代码\C总结前序遍历 题目链接   前序遍历意思就是按照“根节点-左子树-右子树”的顺序来遍历二叉树&#xff0c;通过递归方法来实现…...

【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中的统一内存为此&#xff0c;我向您介绍了统一内存&#xff0c;它可以非常轻松地分配和访问可由系统中任何处理器、CPU 或 GPU 上运行的代码使用的数据。…...

UVM实战--加法器

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

Linux系统点亮LED

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

在superset中快速制作报表或仪表盘

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

【可视化实战】Python 绘制出来的数据大屏真的太惊艳了

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

Obsidium一键编码作业,Obsidia惊人属性

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

约束优化:约束优化的三种序列无约束优化方法

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

RocketMQ快速入门:消息发送、延迟消息、消费重试

一起学编程&#xff0c;让生活更随和&#xff01; 如果你觉得是个同道中人&#xff0c;欢迎关注博主gzh&#xff1a;【随和的皮蛋桑】。 专注于Java基础、进阶、面试以及计算机基础知识分享&#x1f433;。偶尔认知思考、日常水文&#x1f40c;。 目录1、RocketMQ消息结构1.1…...

FANUC机器人通过KAREL程序实现与PLC位置坐标通信的具体方法示例

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

[蓝桥杯 2015 省 B] 移动距离

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

Pandas库入门仅需10分钟

数据处理的时候经常性需要整理出表格&#xff0c;在这里介绍pandas常见使用&#xff0c;目录如下&#xff1a; 数据结构导入导出文件对数据进行操作 – 增加数据&#xff08;创建数据&#xff09; – 删除数据 – 改动数据 – 查找数据 – 常用操作&#xff08;转置&#xff0…...

python的socket通信中,如何设置可以让两台主机通过外网访问?

要让两台主机通过外网进行Socket通信&#xff0c;需要在网络设置和代码实现两个方面进行相应的配置。下面是具体的步骤&#xff1a; 确认网络环境&#xff1a;首先要确保两台主机都能够通过外网访问。可以通过ping命令或者telnet命令来测试两台主机之间是否可以互相访问。 确定…...

检测数据的方法(回顾)

检测数据类型的4种方法typeofinstanceofconstructor{}.toString.call() 检测数据类型的4种方法 typeof 定义 用来检测数据类型的运算符 返回一个字符串&#xff0c;表示操作值的数据类型(7种) number&#xff0c;string&#xff0c;boolean&#xff0c;object&#xff0c;u…...

比特数据结构与算法(第三章_上)栈的概念和实现(力扣:20. 有效的括号)

一、栈&#xff08;stack&#xff09;栈的概念&#xff1a;① 栈是一种特殊的线性表&#xff0c;它只允许在固定的一端进行插入和删除元素的操作。② 进行数据插入的删除和操作的一端&#xff0c;称为栈顶 。另一端则称为 栈底 。③ 栈中的元素遵守后进先出的原则&#xff0c;即…...

矩阵QR分解

1 orthonormal 向量与 Orthogonal 矩阵 orthonormal 向量定义为 &#xff0c;任意向量 相互垂直&#xff0c;且模长为1&#xff1b; 如果将 orthonormal 向量按列组织成矩阵&#xff0c;矩阵为 Orthogonal 矩阵&#xff0c;满足如下性质&#xff1a; &#xff1b; 当为方阵时&…...

Java集合初始化:Lists.newArrayList vs new ArrayList()

文章目录 前言一、核心区别全景图二、代码实现深度对比1. 初始化方式对比2. 容量预分配机制 三、性能与底层原理1. 内存分配策略2. 基准测试数据&#xff08;JMH&#xff09; 四、Guava的进阶功能生态1. 集合转换2. 集合分片3. 不可变集合创建 五、最佳实践指南六、源码级实现解…...

1panel面板中部署SpringBoot和Vue前后端分离系统 【图文教程】

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

基于cornerstone3D的dicom影像浏览器 第二十九章 自定义菜单组件

文章目录 前言一、程序结构1. 菜单数据结构2. XMenu.vue3. XSubMenu.vue4. XSubMenuSlot.vue5. XMenuItem.vue 二、调用流程总结 前言 菜单用于组织程序功能&#xff0c;为用户提供导航。是用户与程序交互非常重要的接口。 开源组件库像Element Plus和Ant Design中都提供了功能…...

#开发环境篇:postMan可以正常调通,但是浏览器里面一直报403

本地header代理下面内容即可 headers: { // 添加必要的请求头 ‘Host’: ‘服务端域名’, ‘Origin’: https://服务端域名, ‘Referer’: https://服务端域名 }, devServer: {// 本地开发代理API地址proxy: {^/file: {target: https://服务端域名,changeOrigin: true, // 是否…...

移动端测试岗位高频面试题及解析

文章目录 一、基础概念二、自动化测试三、性能测试四、专项测试五、安全与稳定性六、高级场景七、实战难题八、其他面题 一、基础概念 移动端测试与Web测试的核心区别&#xff1f; 解析&#xff1a;网络波动&#xff08;弱网测试&#xff09;、设备碎片化&#xff08;机型适配&…...

【Prompt实战】国际翻译小组

本文原创作者&#xff1a;姚瑞南 AI-agent 大模型运营专家/音乐人/野生穿搭model&#xff0c;先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗&#xff1b;多年人工智能行业智能产品运营及大模型落地经验&#xff0c;拥有AI外呼方向国家专利与PMP项目管理证书。&#…...

飞云智能波段主图+多空短线决策副图指标,组合操盘技术图文解说

如上图&#xff0c;组合指标&#xff1a;主图-飞云智能波段&#xff0c;红线上红色K线标记&#xff0c;波段做多.副图指标-多空短线决策&#xff0c;跟踪做短线&#xff0c;红柱做多&#xff0c;绿柱短线卖出或做空。 实战操作中&#xff0c;我们在主图红色线支撑上红色K线出现…...

Delphi 实现远程连接 Access 数据库的指南

方法一&#xff1a;通过局域网共享 Access 文件&#xff08;简单但有限&#xff09; 步骤 1&#xff1a;共享 Access 数据库 将 .mdb 或 .accdb 文件放在局域网内某台电脑的共享文件夹中。 右键文件夹 → 属性 → 共享 → 启用共享并设置权限&#xff08;需允许网络用户读写&a…...

打造高效多模态RAG系统:原理与评测方法详解

引言 随着信息检索与生成式AI的深度融合&#xff0c;检索增强生成&#xff08;RAG, Retrieval-Augmented Generation&#xff09; 已成为AI领域的重要技术方向。传统RAG系统主要依赖文本数据&#xff0c;但真实世界中的信息往往包含图像、表格等多模态内容。多模态RAG&#xf…...