// In order to support react-lifecycles-compat polyfilled components, // Unsafe lifecycles should not be invoked for components using the new APIs. // 不应该使用旧的生命周期钩子 if ( typeof ctor.getDerivedStateFromProps !== 'function' && typeof instance.getSnapshotBeforeUpdate !== 'function' && (typeof instance.UNSAFE_componentWillMount === 'function' || typeof instance.componentWillMount === 'function') ) { # 触发WillMount生命周期钩子 callComponentWillMount(workInProgress, instance); // If we had additional state updates during this life-cycle, let's // process them now. processUpdateQueue(workInProgress, newProps, instance, renderLanes); instance.state = workInProgress.memoizedState; }
# 如果设置了class组件的componentDidMount生命周期钩子函数,则需要在组件的FiberNode上设置对应的flags if (typeof instance.componentDidMount === 'function') { let fiberFlags: Flags = Update; if (enableSuspenseLayoutEffectSemantics) { fiberFlags |= LayoutStatic; }
exportfunctionperformSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null{ if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { thrownewError('Should not already be working.'); }
const didFlushPassiveEffects = flushPassiveEffects(); if (didFlushPassiveEffects) { // If passive effects were flushed, exit to the outer work loop in the root // scheduler, so we can recompute the priority. // TODO: We don't actually need this `ensureRootIsScheduled` call because // this path is only reachable if the root is already part of the schedule. // I'm including it only for consistency with the other exit points from // this function. Can address in a subsequent refactor. ensureRootIsScheduled(root); returnnull; }
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) { syncNestedUpdateFlag(); }
// * setState // !1. render阶段 let exitStatus = renderRootSync(root, lanes); if (root.tag !== LegacyRoot && exitStatus === RootErrored) { // If something threw an error, try rendering one more time. We'll render // synchronously to block concurrent data mutations, and we'll includes // all pending updates are included. If it still fails after the second // attempt, we'll give up and commit the resulting tree. const originallyAttemptedLanes = lanes; const errorRetryLanes = getLanesToRetrySynchronouslyOnError( root, originallyAttemptedLanes, ); if (errorRetryLanes !== NoLanes) { lanes = errorRetryLanes; exitStatus = recoverFromConcurrentError( root, originallyAttemptedLanes, errorRetryLanes, ); } }
if (exitStatus === RootDidNotComplete) { // The render unwound without completing the tree. This happens in special // cases where need to exit the current render without producing a // consistent tree or committing. markRootSuspended(root, lanes, workInProgressDeferredLane); ensureRootIsScheduled(root); returnnull; }
// We now have a consistent tree. Because this is a sync render, we // will commit it even if something suspended. const finishedWork: Fiber = (root.current.alternate: any); root.finishedWork = finishedWork; root.finishedLanes = lanes; // ! 2. commit阶段 commitRoot( root, workInProgressRootRecoverableErrors, workInProgressTransitions, workInProgressRootDidIncludeRecursiveRenderUpdate, workInProgressDeferredLane, );
// Before exiting, make sure there's a callback scheduled for the next // pending level. ensureRootIsScheduled(root);
// This is always non-null on a ClassComponent or HostRoot const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
hasForceUpdate = false;
let firstBaseUpdate = queue.firstBaseUpdate; let lastBaseUpdate = queue.lastBaseUpdate;
// Check if there are pending updates. If so, transfer them to the base queue. // *1. 检查是否有pending update。如果有,需要将它们剪开环型链表,合并到 baseQueue。 // pending update是个单向循环链表,转移到 单链表 firstBaseUpdate->...->lastBaseUpdate 中去 // ! 这里注意pending update不同于baseQueue,pending update只记录了尾节点 let pendingQueue = queue.shared.pending; if (pendingQueue !== null) { // ? sy queue.shared.pending = null;
// The pending queue is circular. Disconnect the pointer between first // and last so that it's non-circular. // ! 这里的 pendingQueue 是在 finishQueueingConcurrentUpdates 函数中构建的单向循环链表 const lastPendingUpdate = pendingQueue; // 尾节点 const firstPendingUpdate = lastPendingUpdate.next; // 头结点 lastPendingUpdate.next = null; // 断开循环链表 // Append pending updates to base queue // 把pending update转移到base queue上 // ! 接下来构建单链表 firstBaseUpdate->...->lastBaseUpdate if (lastBaseUpdate === null) { // base queue是空的,那么firstPendingUpdate就是头节点firstBaseUpdate firstBaseUpdate = firstPendingUpdate; } else { // 否则往这个尾节点后继续加 lastBaseUpdate.next = firstPendingUpdate; } // 更新尾节点, 将 lastBaseUpdate 赋值为 lastPendingUpdate // 此时已经形成了 以 firstBaseUpdate 为头以 lastBaseUpdate 为尾的新链表 // 也即为本次需要处理的 update 链表 lastBaseUpdate = lastPendingUpdate;
// If there's a current queue, and it's different from the base queue, then // we need to transfer the updates to that queue, too. Because the base // queue is a singly-linked list with no cycles, we can append to both // lists and take advantage of structural sharing. // TODO: Pass `current` as argument const current = workInProgress.alternate; // 如果有current queue,并且它和base queue不同,那么我们也需要把更新转移到那个queue上。 if (current !== null) { // This is always non-null on a ClassComponent or HostRoot // 类组件和 HostRoot 的updateQueue都初始化过,所以这里不会是null const currentQueue: UpdateQueue<State> = (current.updateQueue: any); const currentLastBaseUpdate = currentQueue.lastBaseUpdate; // 如果current的lastBaseUpdate和baseQueue的lastBaseUpdate不同,那么把pending update转移到currentQueue上 if (currentLastBaseUpdate !== lastBaseUpdate) { if (currentLastBaseUpdate === null) { currentQueue.firstBaseUpdate = firstPendingUpdate; } else { currentLastBaseUpdate.next = firstPendingUpdate; } currentQueue.lastBaseUpdate = lastPendingUpdate; } } }
// These values may change as we process the queue. if (firstBaseUpdate !== null) { // *2. 接下来要做的就是遍历queue,然后根据这些update,计算出最后的结果。 // Iterate through the list of updates to compute the result. let newState = queue.baseState; // TODO: Don't need to accumulate this. Instead, we can remove renderLanes // from the original lanes. let newLanes = NoLanes; // 这里的 newBaseState, newFirstBaseUpdate,newLastBaseUpdate 是计算的临时变量 // 实际上会用来更新 updateQueue 的 baseState, firstBaseUpdate, lastBaseUpdate let newBaseState = null; let newFirstBaseUpdate = null; let newLastBaseUpdate: null | Update<State> = null;
let update: Update<State> = firstBaseUpdate; do { // An extra OffscreenLane bit is added to updates that were made to // a hidden tree, so that we can distinguish them from updates that were // already there when the tree was hidden. const updateLane = removeLanes(update.lane, OffscreenLane); const isHiddenUpdate = updateLane !== update.lane;
// Check if this update was made while the tree was hidden. If so, then // it's not a "base" update and we should disregard the extra base lanes // that were added to renderLanes when we entered the Offscreen tree. const shouldSkipUpdate = isHiddenUpdate ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane) : !isSubsetOfLanes(renderLanes, updateLane);
// 更新优先级不满足,该 update 会被跳过 if (shouldSkipUpdate) { // ? sy-no // Priority is insufficient. Skip this update. If this is the first // skipped update, the previous update/state is the new base // update/state. const clone: Update<State> = { lane: updateLane,
next: null, }; if (newLastBaseUpdate === null) { newFirstBaseUpdate = newLastBaseUpdate = clone; newBaseState = newState; } else { newLastBaseUpdate = newLastBaseUpdate.next = clone; } // Update the remaining priority in the queue. newLanes = mergeLanes(newLanes, updateLane); } else { // 该 update 更新优先级满足,本次更新不会跳过 // This update does have sufficient priority.
// Check if this update is part of a pending async action. If so, // we'll need to suspend until the action has finished, so that it's // batched together with future updates in the same action.
// 如果 newLastBaseUpdate 不存在,说明之前没有跳过任何 upadte 无需添加新增 // 否则无论无论该 update 是否跳过都需要添加到 baseUpdate 链表之后 if (newLastBaseUpdate !== null) { // ? sy-no const clone: Update<State> = { // This update is going to be committed so we never want uncommit // it. Using NoLane works because 0 is a subset of all bitmasks, so // this will never be skipped by the check above. lane: NoLane,
tag: update.tag, payload: update.payload,
// When this update is rebased, we should not fire its // callback again. callback: null,
if (firstBaseUpdate === null) { // `queue.lanes` is used for entangling transitions. We can set it back to // zero once the queue is empty. // 当多个transitions在同一个queue中时,只允许最近的一个完成。我们不应该显示中间状态。 // 当queue为空,我们将queue.lanes设置回0 queue.shared.lanes = NoLanes; }
// Set the remaining expiration time to be whatever is remaining in the queue. // This should be fine because the only two other things that contribute to // expiration time are props and context. We're already in the middle of the // begin phase by the time we start processing the queue, so we've already // dealt with the props. Context in components that specify // shouldComponentUpdate is tricky; but we'll have to account for // that regardless. // 把跳过的update的lane记录下来 markSkippedUpdateLanes(newLanes); workInProgress.lanes = newLanes; workInProgress.memoizedState = newState; // 更新状态 } }
// Max 31 bit integer. The max integer size in V8 for 32-bit systems. // 最大的31位整数。V8中32位系统的最大整数。 // Math.pow(2, 30) - 1 // 0b111111111111111111111111111111 var maxSigned31BitInt = 1073741823;
// Tasks are stored on a min heap var taskQueue: Array<Task> = []; // 没有延迟的任务 var timerQueue: Array<Task> = []; // 有延迟的任务
// 自增id,标记task唯一性 // Incrementing id counter. Used to maintain insertion order. var taskIdCounter = 1;
// Pausing the scheduler is useful for debugging. var isSchedulerPaused = false;
var currentTask = null; var currentPriorityLevel = NormalPriority;
// 锁 // 是否有work在执行 // This is set while performing work, to prevent re-entrance. var isPerformingWork = false;
// 主线程是否在调度 var isHostCallbackScheduled = false;
let isMessageLoopRunning = false;
// 是否有任务在倒计时 var isHostTimeoutScheduled = false;
let taskTimeoutID: TimeoutID = (-1: any);
let needsPaint = false;
// Capture local references to native APIs, in case a polyfill overrides them. const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null; const localClearTimeout = typeof clearTimeout === 'function' ? clearTimeout : null; const localSetImmediate = typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom
// The main thread has been blocked for a non-negligible amount of time. We // may want to yield control of the main thread, so the browser can perform // high priority tasks. The main ones are painting and user input. If there's // a pending paint or a pending input, then we should yield. But if there's // neither, then we can yield less often while remaining responsive. We'll // eventually yield regardless, since there could be a pending paint that // wasn't accompanied by a call to `requestPaint`, or other main thread tasks // like network events. if (enableIsInputPending) { if (needsPaint) { // There's a pending paint (signaled by `requestPaint`). Yield now. returntrue; } if (timeElapsed < continuousInputInterval) { // We haven't blocked the thread for that long. Only yield if there's a // pending discrete input (e.g. click). It's OK if there's pending // continuous input (e.g. mouseover). if (isInputPending !== null) { return isInputPending(); } } elseif (timeElapsed < maxInterval) { // Yield if there's either a pending discrete or continuous input. if (isInputPending !== null) { return isInputPending(continuousOptions); } } else { // We've blocked the thread for a long time. Even if there's no pending // input, there may be some other scheduled work that we don't know about, // like a network event. Yield now. returntrue; } }
let schedulePerformWorkUntilDeadline; if (typeof localSetImmediate === 'function') { // Node.js and old IE. // There's a few reasons for why we prefer setImmediate. // // Unlike MessageChannel, it doesn't prevent a Node.js process from exiting. // (Even though this is a DOM fork of the Scheduler, you could get here // with a mix of Node.js 15+, which has a MessageChannel, and jsdom.) // https://github.com/facebook/react/issues/20756 // // But also, it runs earlier which is the semantic we want. // If other browsers ever implement it, it's better to use it. // Although both of these would be inferior to native scheduling. schedulePerformWorkUntilDeadline = () => { localSetImmediate(performWorkUntilDeadline); }; } elseif (typeof MessageChannel !== 'undefined') { // DOM and Worker environments. // We prefer MessageChannel because of the 4ms setTimeout clamping. const channel = new MessageChannel(); const port = channel.port2; channel.port1.onmessage = performWorkUntilDeadline; schedulePerformWorkUntilDeadline = () => { port.postMessage(null); }; } else { // We should only fallback here in non-browser environments. schedulePerformWorkUntilDeadline = () => { localSetTimeout(performWorkUntilDeadline, 0); }; }
// 定义超时时间 【根据优先级,设置不同的超时时间】 var timeout; switch (priorityLevel) { case ImmediatePriority: // Times out immediately,立即超时 timeout = -1; break; case UserBlockingPriority: // Eventually times out,最终超时 timeout = userBlockingPriorityTimeout; // 250 break; case IdlePriority: // Never times out,永不超时 timeout = maxSigned31BitInt; // Math.pow(2, 30) - 1, 最大的31位整数。V8中32位系统的最大整数。 break; case LowPriority: // Eventually times out,最终超时 timeout = lowPriorityTimeout; // 10000 break; case NormalPriority: default: // Eventually times out,最终超时 timeout = normalPriorityTimeout; // 5000 break; }
// 过期时间,也是理论上的任务执行时间,值越小,说明优先级越高,需要优先执行 var expirationTime = startTime + timeout;
// 在有效时间内执行工作 const performWorkUntilDeadline = () => { if (isMessageLoopRunning) { const currentTime = getCurrentTime(); // Keep track of the start time so we can measure how long the main thread // has been blocked. // 记录了一个work的起始时间,其实就是一个时间切片的起始时间,是个时间戳 startTime = currentTime;
// If a scheduler task throws, exit the current browser task so the // error can be observed. // // Intentionally not using a try-catch, since that makes some debugging // techniques harder. Instead, if `flushWork` errors, then `hasMoreWork` will // remain true, and we'll continue the work loop. let hasMoreWork = true; try { // 根据返回判断是否还有工作 hasMoreWork = flushWork(currentTime); } finally { if (hasMoreWork) { // 如果还有任务,则又触发调度宏任务事件,生成新的宏任务,即在下一个event loop中继续执行任务 schedulePerformWorkUntilDeadline(); } else { isMessageLoopRunning = false; } } } // Yielding to the browser will give it a chance to paint, so we can // reset this. needsPaint = false; };
functionflushWork(initialTime: number) { // We'll need a host callback the next time work is scheduled. isHostCallbackScheduled = false; if (isHostTimeoutScheduled) { // We scheduled a timeout but it's no longer needed. Cancel it. isHostTimeoutScheduled = false; cancelHostTimeout(); }
// Scheduler periodically yields in case there is other work on the main // thread, like user events. By default, it yields multiple times per frame. // It does not attempt to align with frame boundaries, since most tasks don't // need to be frame aligned; for those that do, use requestAnimationFrame. let frameInterval = frameYieldMs; const continuousInputInterval = continuousYieldMs; const maxInterval = maxYieldMs; let startTime = -1;
functionadvanceTimers(currentTime: number) { // Check for tasks that are no longer delayed and add them to the queue. let timer = peek(timerQueue); while (timer !== null) { if (timer.callback === null) { // 无效的任务 // Timer was cancelled. pop(timerQueue); } elseif (timer.startTime <= currentTime) { // 有效的任务 // 任务已到达开始时间,转入taskQueue中 // Timer fired. Transfer to the task queue. pop(timerQueue); timer.sortIndex = timer.expirationTime; push(taskQueue, timer); } else { // Remaining timers are pending. return; } timer = peek(timerQueue); } }
functioncancelCallback(task: Task) { // Null out the callback to indicate the task has been canceled. (Can't // remove from the queue because you can't remove arbitrary nodes from an // array based heap, only the first one.) task.callback = null; }
functioncommitRoot( root: FiberRoot, recoverableErrors: null | Array<CapturedValue<mixed>>, transitions: Array<Transition> | null, didIncludeRenderPhaseUpdate: boolean, spawnedLane: Lane, ) { // TODO: This no longer makes any sense. We already wrap the mutation and // layout phases. Should be able to remove. const previousUpdateLanePriority = getCurrentUpdatePriority(); const prevTransition = ReactCurrentBatchConfig.transition;
// 根据节点的类型,进行不同的副作用处理 switch (finishedWork.tag) { case FunctionComponent: { if (enableUseEffectEventHook) { if ((flags & Update) !== NoFlags) { commitUseEffectEventMount(finishedWork); } } break; } case ForwardRef: case SimpleMemoComponent: { break; } case ClassComponent: { if ((flags & Snapshot) !== NoFlags) { if (current !== null) { const prevProps = current.memoizedProps; const prevState = current.memoizedState; const instance = finishedWork.stateNode; const snapshot = instance.getSnapshotBeforeUpdate( finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState, ); instance.__reactInternalSnapshotBeforeUpdate = snapshot; } } break; } // hostFiber节点的处理 case HostRoot: { if ((flags & Snapshot) !== NoFlags) { if (supportsMutation) { // 应用根节点 const root = finishedWork.stateNode; // 设置textContent = ''; 也就是清空#div容器内容, 方便Mutation阶段的渲染 clearContainer(root.containerInfo); } } break; } case HostComponent: case HostHoistable: case HostSingleton: case HostText: case HostPortal: case IncompleteClassComponent: // Nothing to do for these component types break; default: { if ((flags & Snapshot) !== NoFlags) { thrownewError( 'This unit of work tag should not have side-effects. This error is ' + 'likely caused by a bug in React. Please file an issue.', ); } } }
if ((flags & Snapshot) !== NoFlags) { resetCurrentDebugFiberInDEV(); } }
// 函数组件处理 case FunctionComponent: case ForwardRef: case MemoComponent: case SimpleMemoComponent: { // recursivelyTraverse:递归遍历 recursivelyTraverseMutationEffects(root, finishedWork, lanes); // app组件跳出了循环,向下继续执行 commitReconciliationEffects(finishedWork);
// 如果flgas标记为Update更新 if (flags & Update) { // 取出当前节点对应的DOM对象 const instance: Instance = finishedWork.stateNode; if (instance != null) { // Commit the work prepared earlier. const newProps = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps // as the newProps. The updatePayload will contain the real change in // this case. const oldProps = current !== null ? current.memoizedProps : newProps; const type = finishedWork.type; // TODO: Type the updateQueue to be specific to host components. const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any); finishedWork.updateQueue = null; if (updatePayload !== null) { try { commitUpdate( instance, updatePayload, type, oldProps, newProps, finishedWork, ); } catch (error) { captureCommitPhaseError( finishedWork, finishedWork.return, error, ); } } } } } return; }
// 文本处理 case HostText: { recursivelyTraverseMutationEffects(root, finishedWork, lanes); commitReconciliationEffects(finishedWork);
if (flags & Update) { if (supportsMutation) { if (finishedWork.stateNode === null) { thrownewError( 'This should have a text node initialized. This error is likely ' + 'caused by a bug in React. Please file an issue.', ); }
const textInstance: TextInstance = finishedWork.stateNode; const newText: string = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps // as the newProps. The updatePayload will contain the real change in // this case. const oldText: string = current !== null ? current.memoizedProps : newText;
// This is the entry point for every concurrent task, i.e. anything that goes through Scheduler. exportfunctionperformConcurrentWorkOnRoot( root: FiberRoot, didTimeout: boolean, ): RenderTaskFn | null{ if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { thrownewError('Should not already be working.'); }
// Flush any pending passive effects before deciding which lanes to work on, // in case they schedule additional work. const originalCallbackNode = root.callbackNode; // ?? const didFlushPassiveEffects = flushPassiveEffects(); if (didFlushPassiveEffects) { // Something in the passive effect phase may have canceled the current task. // Check if the task node for this root was changed. if (root.callbackNode !== originalCallbackNode) { // The current task was canceled. Exit. We don't need to call // `ensureRootIsScheduled` because the check above implies either that // there's a new task, or that there's no remaining work on this root. returnnull; } else { // Current task was not canceled. Continue. } }
// Determine the next lanes to work on, using the fields stored // on the root. // TODO: This was already computed in the caller. Pass it as an argument. let lanes = getNextLanes( root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes, );
if (lanes === NoLanes) { // Defensive coding. This is never expected to happen. returnnull; }
// We disable time-slicing in some cases: if the work has been CPU-bound // for too long ("expired" work, to prevent starvation), or we're in // sync-updates-by-default mode. // 在某些情况下,我们会禁用时间片切片:如果work过长时间做计算(为了防止饥饿而将其视为“过期”的work),或者我们处于默认启用同步更新模式。 // TODO: We only check `didTimeout` defensively, to account for a Scheduler // bug we're still investigating. Once the bug in Scheduler is fixed, // we can remove this, since we track expiration ourselves. const shouldTimeSlice = !includesBlockingLane(root, lanes) && !includesExpiredLane(root, lanes) && // 参考这个例子,UseDeferredValuePage (disableSchedulerTimeoutInWorkLoop || !didTimeout);
if (exitStatus !== RootInProgress) { let renderWasConcurrent = shouldTimeSlice; do { if (exitStatus === RootDidNotComplete) { // The render unwound without completing the tree. This happens in special // cases where need to exit the current render without producing a // consistent tree or committing. markRootSuspended(root, lanes, NoLane); } else { // ! 2. render结束,做一些检查 // The render completed.
// Check if this render may have yielded to a concurrent event, and if so, // confirm that any newly rendered stores are consistent. // TODO: It's possible that even a concurrent render may never have yielded // to the main thread, if it was fast enough, or if it expired. We could // skip the consistency check in that case, too. const finishedWork: Fiber = (root.current.alternate: any); if ( renderWasConcurrent && !isRenderConsistentWithExternalStores(finishedWork) ) { // A store was mutated in an interleaved event. Render again, // synchronously, to block further mutations. exitStatus = renderRootSync(root, lanes); // We assume the tree is now consistent because we didn't yield to any // concurrent events. renderWasConcurrent = false; // Need to check the exit status again. continue; }
// If the root or lanes have changed, throw out the existing stack // and prepare a fresh one. Otherwise we'll continue where we left off. if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) { if (enableUpdaterTracking) { if (isDevToolsPresent) { const memoizedUpdaters = root.memoizedUpdaters; if (memoizedUpdaters.size > 0) { restorePendingUpdaters(root, workInProgressRootRenderLanes); memoizedUpdaters.clear(); }
// At this point, move Fibers that scheduled the upcoming work from the Map to the Set. // If we bailout on this work, we'll move them back (like above). // It's important to move them now in case the work spawns more work at the same priority with different updaters. // That way we can keep the current update and future updates separate. movePendingFibersToMemoized(root, lanes); } }
if (enableSchedulingProfiler) { markRenderStarted(lanes); }
let didSuspendInShell = false; outer: do { try { if ( workInProgressSuspendedReason !== NotSuspended && workInProgress !== null ) { // The work loop is suspended. During a synchronous render, we don't // yield to the main thread. Immediately unwind the stack. This will // trigger either a fallback or an error boundary. // TODO: For discrete and "default" updates (anything that's not // flushSync), we want to wait for the microtasks the flush before // unwinding. Will probably implement this using renderRootConcurrent, // or merge renderRootSync and renderRootConcurrent into the same // function and fork the behavior some other way. const unitOfWork = workInProgress; const thrownValue = workInProgressThrownValue; switch (workInProgressSuspendedReason) { case SuspendedOnHydration: { // Selective hydration. An update flowed into a dehydrated tree. // Interrupt the current render so the work loop can switch to the // hydration lane. resetWorkInProgressStack(); workInProgressRootExitStatus = RootDidNotComplete; break outer; } case SuspendedOnImmediate: case SuspendedOnData: { if (!didSuspendInShell && getSuspenseHandler() === null) { didSuspendInShell = true; } // Intentional fallthrough } default: { // Unwind then continue with the normal work loop. workInProgressSuspendedReason = NotSuspended; workInProgressThrownValue = null; throwAndUnwindWorkLoop(root, unitOfWork, thrownValue); break; } } } // ! 4. workLoopSync workLoopSync(); break; } catch (thrownValue) { handleThrow(root, thrownValue); } } while (true); // Check if something suspended in the shell. We use this to detect an // infinite ping loop caused by an uncached promise. // // Only increment this counter once per synchronous render attempt across the // whole tree. Even if there are many sibling components that suspend, this // counter only gets incremented once. if (didSuspendInShell) { root.shellSuspendCounter++; } // ! 5. 重置 // 重置Context的相关值,如currentlyRenderingFiber等 resetContextDependencies(); // 重置 executionContext executionContext = prevExecutionContext;
if (workInProgress !== null) { // This is a sync render, so we should have finished the whole tree. thrownewError( 'Cannot commit an incomplete root. This error is likely caused by a ' + 'bug in React. Please file an issue.', ); }
if (enableSchedulingProfiler) { markRenderStopped(); }
// Set this to null to indicate there's no in-progress render. // 设置为null,表示没有进行中的render了 workInProgressRoot = null; workInProgressRootRenderLanes = NoLanes;
// ! 6. 再遍历一遍更新队列 // It's safe to process the queue now that the render phase is complete. finishQueueingConcurrentUpdates();
const timeoutHandle = root.timeoutHandle; if (timeoutHandle !== noTimeout) { // The root previous suspended and scheduled a timeout to commit a fallback // state. Now that we have additional work, cancel the timeout. root.timeoutHandle = noTimeout; // $FlowFixMe[incompatible-call] Complains noTimeout is not a TimeoutID, despite the check above cancelTimeout(timeoutHandle); } const cancelPendingCommit = root.cancelPendingCommit; if (cancelPendingCommit !== null) { root.cancelPendingCommit = null; cancelPendingCommit(); }
// Get the lanes that are entangled with whatever we're about to render. We // track these separately so we can distinguish the priority of the render // task from the priority of the lanes it is entangled with. For example, a // transition may not be allowed to finish unless it includes the Sync lane, // which is currently suspended. We should be able to render the Transition // and Sync lane in the same batch, but at Transition priority, because the // Sync lane already suspended.
functionworkLoopSync() { // Perform work without checking if we need to yield between fiber. while (workInProgress !== null) { performUnitOfWork(workInProgress); } }
functionworkLoopConcurrent() { // Perform work until Scheduler asks us to yield while (workInProgress !== null && !shouldYield()) { performUnitOfWork(workInProgress); } }
if ( oldProps !== newProps || hasLegacyContextChanged() || // Force a re-render if the implementation changed due to hot reload: (__DEV__ ? workInProgress.type !== current.type : false) ) { // If props or context changed, mark the fiber as having performed work. // This may be unset if the props are determined to be equal later (memo). didReceiveUpdate = true; } else { // Neither props nor legacy context changes. Check if there's a pending // update or context change. const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext( current, renderLanes, ); if ( !hasScheduledUpdateOrContext && // If this is the second pass of an error or suspense boundary, there // may not be work scheduled on `current`, so we check for this flag. (workInProgress.flags & DidCapture) === NoFlags ) { // No pending updates or context. Bail out now. didReceiveUpdate = false; return attemptEarlyBailoutIfNoScheduledUpdate( current, workInProgress, renderLanes, ); } if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) { // This is a special case that only exists for legacy mode. // See https://github.com/facebook/react/pull/19216. didReceiveUpdate = true; } else { // An update was scheduled on this fiber, but there are no new props // nor legacy context. Set this to false. If an update queue or context // consumer produces a changed value, it will set this to true. Otherwise, // the component will assume the children have not changed and bail out. didReceiveUpdate = false; } } } else { didReceiveUpdate = false;
if (getIsHydrating() && isForkedChild(workInProgress)) { // Check if this child belongs to a list of muliple children in // its parent. // // In a true multi-threaded implementation, we would render children on // parallel threads. This would represent the beginning of a new render // thread for this subtree. // // We only use this for id generation during hydration, which is why the // logic is located in this special branch. const slotIndex = workInProgress.index; const numberOfForks = getForksAtLevel(workInProgress); pushTreeId(workInProgress, numberOfForks, slotIndex); } }
// Before entering the begin phase, clear pending update priority. // TODO: This assumes that we're about to evaluate the component and process // the update queue. However, there's an exception: SimpleMemoComponent // sometimes bails out later in the begin phase. This indicates that we should // move this assignment out of the common path and into each branch. // 在进入开始阶段之前,清除待处理的更新优先级。 // TODO: 这假设我们即将评估组件并处理更新队列。然而,有一个例外情况:SimpleMemoComponent // 有时会在开始阶段后退出。这表明我们应该将这个赋值移出常规路径,放到每个分支中。 workInProgress.lanes = NoLanes;
switch (workInProgress.tag) { case IndeterminateComponent: { return mountIndeterminateComponent( current, workInProgress, workInProgress.type, renderLanes, ); } case LazyComponent: { const elementType = workInProgress.elementType; return mountLazyComponent( current, workInProgress, elementType, renderLanes, ); } // 函数组件 case FunctionComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateFunctionComponent( current, workInProgress, Component, resolvedProps, renderLanes, ); } // 类组件 case ClassComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateClassComponent( current, workInProgress, Component, resolvedProps, renderLanes, ); } // 根Fiber(第一次都会走这里) case HostRoot: return updateHostRoot(current, workInProgress, renderLanes); case HostHoistable: if (enableFloat && supportsResources) { return updateHostHoistable(current, workInProgress, renderLanes); } case HostSingleton: if (supportsSingletons) { return updateHostSingleton(current, workInProgress, renderLanes); } // 原生标签,div span等标签 case HostComponent: return updateHostComponent(current, workInProgress, renderLanes); case HostText: return updateHostText(current, workInProgress); case SuspenseComponent: return updateSuspenseComponent(current, workInProgress, renderLanes); case HostPortal: return updatePortalComponent(current, workInProgress, renderLanes); case ForwardRef: { const type = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === type ? unresolvedProps : resolveDefaultProps(type, unresolvedProps); return updateForwardRef( current, workInProgress, type, resolvedProps, renderLanes, ); } case Fragment: return updateFragment(current, workInProgress, renderLanes); case Mode: return updateMode(current, workInProgress, renderLanes); case Profiler: return updateProfiler(current, workInProgress, renderLanes); case ContextProvider: return updateContextProvider(current, workInProgress, renderLanes); case ContextConsumer: return updateContextConsumer(current, workInProgress, renderLanes); case MemoComponent: { const type = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; // Resolve outer props first, then resolve inner props. let resolvedProps = resolveDefaultProps(type, unresolvedProps); resolvedProps = resolveDefaultProps(type.type, resolvedProps); return updateMemoComponent( current, workInProgress, type, resolvedProps, renderLanes, ); } case SimpleMemoComponent: { return updateSimpleMemoComponent( current, workInProgress, workInProgress.type, workInProgress.pendingProps, renderLanes, ); } case IncompleteClassComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return mountIncompleteClassComponent( current, workInProgress, Component, resolvedProps, renderLanes, ); } case SuspenseListComponent: { return updateSuspenseListComponent(current, workInProgress, renderLanes); } case ScopeComponent: { if (enableScopeAPI) { return updateScopeComponent(current, workInProgress, renderLanes); } break; } case OffscreenComponent: { return updateOffscreenComponent(current, workInProgress, renderLanes); } case LegacyHiddenComponent: { if (enableLegacyHidden) { return updateLegacyHiddenComponent( current, workInProgress, renderLanes, ); } break; } case CacheComponent: { if (enableCache) { return updateCacheComponent(current, workInProgress, renderLanes); } break; } case TracingMarkerComponent: { if (enableTransitionTracing) { return updateTracingMarkerComponent( current, workInProgress, renderLanes, ); } break; } }
thrownewError( `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` + 'React. Please file an issue.', ); }
if (current === null) { tryToClaimNextHydratableInstance(workInProgress); }
const type = workInProgress.type; const nextProps = workInProgress.pendingProps; const prevProps = current !== null ? current.memoizedProps : null;
let nextChildren = nextProps.children; const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) { // We special case a direct text child of a host node. This is a common // case. We won't handle it as a reified child. We will instead handle // this in the host environment that also has access to this prop. That // avoids allocating another HostText fiber and traversing it. nextChildren = null; } elseif (prevProps !== null && shouldSetTextContent(type, prevProps)) { // If we're switching from a direct text child to a normal child, or to // empty, we need to schedule the text content to be reset. workInProgress.flags |= ContentReset; }
if (enableFormActions && enableAsyncActions) { const memoizedState = workInProgress.memoizedState; if (memoizedState !== null) { // This fiber has been upgraded to a stateful component. The only way // happens currently is for form actions. We use hooks to track the // pending and error state of the form. // // Once a fiber is upgraded to be stateful, it remains stateful for the // rest of its lifetime. const newState = renderTransitionAwareHostComponentWithHooks( current, workInProgress, renderLanes, );
// If the transition state changed, propagate the change to all the // descendents. We use Context as an implementation detail for this. // // This is intentionally set here instead of pushHostContext because // pushHostContext gets called before we process the state hook, to avoid // a state mismatch in the event that something suspends. // // NOTE: This assumes that there cannot be nested transition providers, // because the only renderer that implements this feature is React DOM, // and forms cannot be nested. If we did support nested providers, then // we would need to push a context value even for host fibers that // haven't been upgraded yet. if (isPrimaryRenderer) { HostTransitionContext._currentValue = newState; } else { HostTransitionContext._currentValue2 = newState; } if (enableLazyContextPropagation) { // In the lazy propagation implementation, we don't scan for matching // consumers until something bails out. } else { if (didReceiveUpdate) { if (current !== null) { const oldStateHook: Hook = current.memoizedState; const oldState: TransitionStatus = oldStateHook.memoizedState; // This uses regular equality instead of Object.is because we assume // that host transition state doesn't include NaN as a valid type. if (oldState !== newState) { propagateContextChange( workInProgress, HostTransitionContext, renderLanes, ); } } } } } }
// Usables are a valid React node type. When React encounters a Usable in // a child position, it unwraps it using the same algorithm as `use`. For // example, for promises, React will throw an exception to unwind the // stack, then replay the component once the promise resolves. // // A difference from `use` is that React will keep unwrapping the value // until it reaches a non-Usable type. // // e.g. Usable<Usable<Usable<T>>> should resolve to T // // The structure is a bit unfortunate. Ideally, we shouldn't need to // replay the entire begin phase of the parent fiber in order to reconcile // the children again. This would require a somewhat significant refactor, // because reconcilation happens deep within the begin phase, and // depending on the type of work, not always at the end. We should // consider as an future improvement. if (typeof newChild.then === 'function') { const thenable: Thenable<any> = (newChild: any); return reconcileChildFibersImpl( returnFiber, currentFirstChild, unwrapThenable(thenable), lanes, mergeDebugInfo(debugInfo, thenable._debugInfo), ); }
let oldFiber = currentFirstChild; // 旧的Fiber节点 let lastPlacedIndex = 0; let newIdx = 0; let nextOldFiber = null; // ! 1. 从左边往右遍历,比较新老节点,如果节点可以复用,继续往右,否则就停止 for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { if (oldFiber.index > newIdx) { nextOldFiber = oldFiber; oldFiber = null; } else { nextOldFiber = oldFiber.sibling; } const newFiber = updateSlot( returnFiber, oldFiber, newChildren[newIdx], lanes, debugInfo, ); if (newFiber === null) { // TODO: This breaks on empty slots like null children. That's // unfortunate because it triggers the slow path all the time. We need // a better way to communicate whether this was a miss or null, // boolean, undefined, etc. if (oldFiber === null) { oldFiber = nextOldFiber; } break; } if (shouldTrackSideEffects) { if (oldFiber && newFiber.alternate === null) { // We matched the slot, but we didn't reuse the existing fiber, so we // need to delete the existing child. deleteChild(returnFiber, oldFiber); } } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { // TODO: Move out of the loop. This only happens for the first run. resultingFirstChild = newFiber; } else { // TODO: Defer siblings if we're not at the right index for this slot. // I.e. if we had null values before, then we want to defer this // for each null value. However, we also don't want to call updateSlot // with the previous one. previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; oldFiber = nextOldFiber; }
// !2.1 新节点没了,(老节点还有)。则删除剩余的老节点即可 // 0 1 2 3 4 // 0 1 2 3 if (newIdx === newChildren.length) { // We've reached the end of the new children. We can delete the rest. deleteRemainingChildren(returnFiber, oldFiber); if (getIsHydrating()) { const numberOfForks = newIdx; pushTreeFork(returnFiber, numberOfForks); } return resultingFirstChild; } // ! 2.2 (新节点还有),老节点没了 // 0 1 2 3 4 // 0 1 2 3 4 5 if (oldFiber === null) { // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; newIdx < newChildren.length; newIdx++) { const newFiber = createChild( returnFiber, newChildren[newIdx], lanes, debugInfo, ); if (newFiber === null) { continue; } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { // TODO: Move out of the loop. This only happens for the first run. resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; } if (getIsHydrating()) { const numberOfForks = newIdx; pushTreeFork(returnFiber, numberOfForks); } return resultingFirstChild; }
// !2.3 新老节点都还有节点,但是因为老fiber是链表,不方便快速get与delete, // ! 因此把老fiber链表中的节点放入Map中,后续操作这个Map的get与delete // 0 1| 4 5 // 0 1| 7 8 2 3 // Add all children to a key map for quick lookups. const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// Keep scanning and use the map to restore deleted items as moves. for (; newIdx < newChildren.length; newIdx++) { const newFiber = updateFromMap( existingChildren, returnFiber, newIdx, newChildren[newIdx], lanes, debugInfo, ); if (newFiber !== null) { if (shouldTrackSideEffects) { if (newFiber.alternate !== null) { // The new fiber is a work in progress, but if there exists a // current, that means that we reused the fiber. We need to delete // it from the child list so that we don't add it to the deletion // list. existingChildren.delete( newFiber.key === null ? newIdx : newFiber.key, ); } } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; } }
// !3. 如果是组件更新阶段,此时新节点已经遍历完了,能复用的老节点都用完了, // ! 则最后查找Map里是否还有元素,如果有,则证明是新节点里不能复用的,也就是要被删除的元素,此时删除这些元素就可以了 if (shouldTrackSideEffects) { // Any existing children that weren't consumed above were deleted. We need // to add them to the deletion list. existingChildren.forEach(child => deleteChild(returnFiber, child)); }
// Set this to null to indicate there's no in-progress render. // 设置为null,表示没有进行中的render了 workInProgressRoot = null; workInProgressRootRenderLanes = NoLanes;
// Track lanes that were updated during the render phase workInProgressRootRenderPhaseUpdatedLanes = mergeLanes( workInProgressRootRenderPhaseUpdatedLanes, lane, ); } else {
// ...
// 页面初次渲染时候,此时workInProgressRoot还是null 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. if ((executionContext & RenderContext) === NoContext) { workInProgressRootInterleavedUpdatedLanes = mergeLanes( workInProgressRootInterleavedUpdatedLanes, 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, workInProgressDeferredLane, ); } }
// 2. 开始调度 ensureRootIsScheduled(root);
if ( lane === SyncLane && executionContext === NoContext && (fiber.mode & ConcurrentMode) === NoMode ) { if (__DEV__ && ReactCurrentActQueue.isBatchingLegacy) { // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode. } else { // 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(); flushSyncWorkOnLegacyRootsOnly(); } } } }
// 在当前事件结束时,逐个检查每个root,并确保为每个root安排了正确优先级的任务。 if (__DEV__ && ReactCurrentActQueue.current !== null) { // ... } else { if (!didScheduleMicrotask) { // ? sy didScheduleMicrotask = true; scheduleImmediateTask(processRootScheduleInMicrotask); } }
if (!enableDeferRootSchedulingToMicrotask) { // While this flag is disabled, we schedule the render task immediately // instead of waiting a microtask. // TODO: We need to land enableDeferRootSchedulingToMicrotask ASAP to // unblock additional features we have planned. scheduleTaskForRootDuringMicrotask(root, now()); } }
functionscheduleImmediateTask(cb: () => mixed) { // TODO: Can we land supportsMicrotasks? Which environments don't support it? // Alternatively, can we move this check to the host config? if (supportsMicrotasks) { // sy- scheduleMicrotask(() => { // In Safari, appending an iframe forces microtasks to run. // 在Safari中,附加一个iframe会强制微任务运行。 // https://github.com/facebook/react/issues/22459 // We don't support running callbacks in the middle of render // or commit so we need to check against that. // 在渲染或提交过程中我们不支持运行回调函数,因此我们需要进行检查。 const executionContext = getExecutionContext(); if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { // sy-no // Note that this would still prematurely flush the callbacks // if this happens outside render or commit phase (e.g. in an event). // 如果这种情况发生在render或commit阶段之外(例如在事件中),这仍然会过早地刷新回调函数。
// Intentionally using a macrotask instead of a microtask here. This is // wrong semantically but it prevents an infinite loop. The bug is // Safari's, not ours, so we just do our best to not crash even though // the behavior isn't completely correct. // 在这里故意使用宏任务而不是微任务。这在语义上是错误的,但它可以防止无限循环。 // 这个 bug 是 Safari 的问题,而不是我们的问题,所以我们尽力避免崩溃,即使行为并不完全正确。 Scheduler_scheduleCallback(ImmediateSchedulerPriority, cb); return; } cb(); }); } else { // If microtasks are not supported, use Scheduler. Scheduler_scheduleCallback(ImmediateSchedulerPriority, cb); } }
let prev = null; let root = firstScheduledRoot; while (root !== null) {
const next = root.next;
if ( currentEventTransitionLane !== NoLane && shouldAttemptEagerTransition() ) { // A transition was scheduled during an event, but we're going to try to // render it synchronously anyway. We do this during a popstate event to // preserve the scroll position of the previous page. upgradePendingLaneToSync(root, currentEventTransitionLane); }
const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime); if (nextLanes === NoLane) { // ? sy second // 页面初次渲染,再执行这里。nextLanes=0 // This root has no more pending work. Remove it from the schedule. To // guard against subtle reentrancy bugs, this microtask is the only place // we do this — you can add roots to the schedule whenever, but you can // only remove them here.
// Null this out so we know it's been removed from the schedule. //root 没有更多pending work。从调度中移除它。为了防止微妙的重入bug,这个microtask是我们唯一执行此操作的地方 — 你可以随时将roots添加到调度中,但只能在这里将它们移除。 // 将其置null,以便我们知道它已从调度中移除 root.next = null; if (prev === null) { // ? sy // This is the new head of the list firstScheduledRoot = next; } else { prev.next = next; } if (next === null) { // ? sy // This is the new tail of the list lastScheduledRoot = prev; } } else { // ? sy first // 页面初次渲染,先执行这里。nextLanes=32 // This root still has work. Keep it in the list. prev = root; if (includesSyncLane(nextLanes)) { // ? sy-no mightHavePendingSyncWork = true; } } root = next; }
currentEventTransitionLane = NoLane;
// At the end of the microtask, flush any pending synchronous work. This has // to come at the end, because it does actual rendering work that might throw. // 在microtask结束时,flush任何pending的同步work。这必须放在最后,因为它执行实际的可能会抛出异常的渲染工作。 // onClick count flushSyncWorkOnAllRoots(); }
// Determine the next lanes to work on, and their priority. const workInProgressRoot = getWorkInProgressRoot(); const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();
const existingCallbackNode = root.callbackNode; // 如果优先级等于0 ,说明根节点没有可处理的回调,则退出任务调度 if ( // Check if there's nothing to work on nextLanes === NoLanes || // If this root is currently suspended and waiting for data to resolve, don't // schedule a task to render it. We'll either wait for a ping, or wait to // receive an update. // // Suspended render phase (root === workInProgressRoot && isWorkLoopSuspendedOnData()) || // Suspended commit phase root.cancelPendingCommit !== null ) { // Fast path: There's nothing to work on. if (existingCallbackNode !== null) { cancelCallback(existingCallbackNode); } root.callbackNode = null; root.callbackPriority = NoLane; return NoLane; }
// Schedule a new callback in the host environment. if (includesSyncLane(nextLanes)) { // sy- setState // sy-no 初次渲染 // Synchronous work is always flushed at the end of the microtask, so we // don't need to schedule an additional task. // 同步工作始终在微任务结束时刷新,因此我们不需要安排额外的任务。 if (existingCallbackNode !== null) { cancelCallback(existingCallbackNode); } root.callbackPriority = SyncLane; root.callbackNode = null; return SyncLane; } else { // We use the highest priority lane to represent the priority of the callback. const existingCallbackPriority = root.callbackPriority; const newCallbackPriority = getHighestPriorityLane(nextLanes);
if ( newCallbackPriority === existingCallbackPriority && // Special case related to `act`. If the currently scheduled task is a // Scheduler task, rather than an `act` task, cancel it and re-schedule // on the `act` queue. !( __DEV__ && ReactCurrentActQueue.current !== null && existingCallbackNode !== fakeActCallbackNode ) ) { // The priority hasn't changed. We can reuse the existing task. return newCallbackPriority; } else { // Cancel the existing callback. We'll schedule a new one below. cancelCallback(existingCallbackNode); }
// 定义一个【调度的优先级】,因为react事件优先级和scheduler的优先级不同,需要经过转换【即:事件优先级转换为调度优先级】 let schedulerPriorityLevel; // lanes转换成事件优先级,匹配符合的优先级,然后赋值对应的scheduler的优先级 switch (lanesToEventPriority(nextLanes)) { case DiscreteEventPriority: schedulerPriorityLevel = ImmediateSchedulerPriority; break; case ContinuousEventPriority: schedulerPriorityLevel = UserBlockingSchedulerPriority; break; case DefaultEventPriority: // 32 // ? sy 页面初次渲染、transition(128) schedulerPriorityLevel = NormalSchedulerPriority; // 3 break; case IdleEventPriority: schedulerPriorityLevel = IdleSchedulerPriority; break; default: schedulerPriorityLevel = NormalSchedulerPriority; break; }
export function createRoot( container: Element | Document | DocumentFragment, options?: CreateRootOptions, // 有两个参数可用,其余的属于非稳定值,不要使用 ): RootType { // 1. 检查container是否是DOM if (!isValidContainer(container)) { throw new Error('createRoot(...): Target container is not a DOM element.'); }
warnIfReactDOMContainerInDEV(container);
let isStrictMode = false; let concurrentUpdatesByDefaultOverride = false; let identifierPrefix = ''; let onRecoverableError = defaultOnRecoverableError; let transitionCallbacks = null;
exportfunctioncreateFiberRoot( containerInfo: Container, tag: RootTag, hydrate: boolean, initialChildren: ReactNodeList, hydrationCallbacks: null | SuspenseHydrationCallbacks, isStrictMode: boolean, concurrentUpdatesByDefaultOverride: null | boolean, // TODO: We have several of these arguments that are conceptually part of the // host config, but because they are passed in at runtime, we have to thread // them through the root constructor. Perhaps we should put them all into a // single type, like a DynamicHostConfig that is defined by the renderer. identifierPrefix: string, onRecoverableError: null | ((error: mixed) => void), transitionCallbacks: null | TransitionTracingCallbacks, formState: ReactFormState<any, any> | null, ): FiberRoot{ // 1. fiberRoot 表示Fiber数据结构对象, 是Fiber数据结构中的最外层对象。 const root: FiberRoot = (new FiberRootNode( containerInfo, tag, hydrate, identifierPrefix, onRecoverableError, formState, ): any); // 2. rootFiber表示组件挂载点对应的Fiber对象,比如React应用中默认的组件挂载点就是id为root的div。 const uninitializedFiber: Fiber = createHostRootFiber( tag, isStrictMode, concurrentUpdatesByDefaultOverride, ); // 3. 循环构造root 和 uninitializedFiber // fiberRoot包含rootFiber,在fiberRoot对象中有一个current属性,存储rootFiber。 root.current = uninitializedFiber; // rootFiber指向fiberRoot,在rootFiber对象中有一个stateNode属性,指向fiberRoot。 uninitializedFiber.stateNode = root;
if (enableCache) { // sy const initialCache = createCache(); retainCache(initialCache);
// The pooledCache is a fresh cache instance that is used temporarily // for newly mounted boundaries during a render. In general, the // pooledCache is always cleared from the root at the end of a render: // it is either released when render commits, or moved to an Offscreen // component if rendering suspends. Because the lifetime of the pooled // cache is distinct from the main memoizedState.cache, it must be // retained separately. root.pooledCache = initialCache; retainCache(initialCache); const initialState: RootState = { element: initialChildren, isDehydrated: hydrate, cache: initialCache, }; uninitializedFiber.memoizedState = initialState; } else { const initialState: RootState = { element: initialChildren, isDehydrated: hydrate, cache: (null: any), // not enabled yet }; uninitializedFiber.memoizedState = initialState; }
exportfunctioncreateHostRootFiber( tag: RootTag, isStrictMode: boolean, concurrentUpdatesByDefaultOverride: null | boolean, ): Fiber{ let mode; if (tag === ConcurrentRoot) { mode = ConcurrentMode; if (isStrictMode === true) { mode |= StrictLegacyMode | StrictEffectsMode; } if ( // We only use this flag for our repo tests to check both behaviors. forceConcurrentByDefaultForTesting ) { mode |= ConcurrentUpdatesByDefaultMode; } elseif ( // Only for internal experiments. allowConcurrentByDefault && concurrentUpdatesByDefaultOverride ) { mode |= ConcurrentUpdatesByDefaultMode; } } else { mode = NoMode; }
if (enableProfilerTimer && isDevToolsPresent) { // Always collect profile timings when DevTools are present. // This enables DevTools to start capturing timing at any point– // Without some nodes in the tree having empty base times. mode |= ProfileMode; }
// ! 2. 创建update const update = createUpdate(lane); // Caution: React DevTools currently depends on this property // being called "element". update.payload = {element};
exportfunctionrequestUpdateLane(fiber: Fiber): Lane{ // Special cases const mode = fiber.mode; // 1. 非ConcurrentMode模式 2. 目前不支持 if ((mode & ConcurrentMode) === NoMode) { return (SyncLane: Lane); } elseif ( (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" (lanes) 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); }
// 普通更新与非紧急更新(18) const transition = requestCurrentTransition(); // 如果有transition if (transition !== null) { const actionScopeLane = peekEntangledActionLane(); return actionScopeLane !== NoLane ? // We're inside an async action scope. Reuse the same lane. actionScopeLane : // We may or may not be inside an async action scope. If we are, this // is the first update in that scope. Either way, we need to get a // fresh transition lane. requestTransitionLane(transition); }
// TODO: Move this type conversion to the event priority module. // React内部的一些update,比如flushSync,会通过上下文变量来跟踪其优先级 const updateLane: Lane = (getCurrentUpdatePriority(): any); if (updateLane !== NoLane) { // ? sy setState click 2 return updateLane; }
// TODO: Move this type conversion to the event priority module. // React外部的update,根据事件类型,向当前环境获取对应的优先级。 const eventLane: Lane = (getCurrentEventPriority(): any); return eventLane; }
// 类组件旧的生命周期相关的update if (isUnsafeClassRenderPhaseUpdate(fiber)) { // This is an unsafe render phase update. Add directly to the update // queue so we can process it immediately during the current render. const pending = sharedQueue.pending; if (pending === null) { // This is the first update. Create a circular list. update.next = update; } else { update.next = pending.next; pending.next = update; } sharedQueue.pending = update;
// Update the childLanes even though we're most likely already rendering // this fiber. This is for backwards compatibility in the case where you // update a different component during render phase than the one that is // currently renderings (a pattern that is accompanied by a warning). return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane); } else { // sy return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane); } }
functionenqueueUpdate( fiber: Fiber, queue: ConcurrentQueue | null, update: ConcurrentUpdate | null, lane: Lane, ) { // Don't update the `childLanes` on the return path yet. If we already in // the middle of rendering, wait until after it has completed. concurrentQueues[concurrentQueuesIndex++] = fiber; concurrentQueues[concurrentQueuesIndex++] = queue; concurrentQueues[concurrentQueuesIndex++] = update; concurrentQueues[concurrentQueuesIndex++] = lane;
// The fiber's `lane` field is used in some places to check if any work is // scheduled, to perform an eager bailout, so we need to update it immediately. // TODO: We should probably move this to the "shared" queue instead. fiber.lanes = mergeLanes(fiber.lanes, lane); const alternate = fiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, lane); } }
// When a setState happens, we must ensure the root is scheduled. Because // update queues do not have a backpointer to the root, the only way to do // this currently is to walk up the return path. This used to not be a big // deal because we would have to walk up the return path to set // the `childLanes`, anyway, but now those two traversals happen at // different times. // TODO: Consider adding a `root` backpointer on the update queue. // __DEV__,检查是否有未挂载的Fiber,如Can't perform a React state update on a component that hasn't mounted yet. detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber); let node = sourceFiber; let parent = node.return; // 循环往上查找,找到根节点 while (parent !== null) { detectUpdateOnUnmountedFiber(sourceFiber, node); node = parent; parent = node.return; }
functionflushJobs(seen?: CountMap) { isFlushPending = false isFlushing = true if (__DEV__) { seen = seen || newMap() }
// Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child so its render effect will have smaller // priority number) // 2. If a component is unmounted during a parent component's update, // its update can be skipped. // 组件更新的顺序是从父到子 因为父组件总是在子组件之前创建 所以它的渲染效果将具有更小的优先级 // 如果一个组件在父组件更新期间被卸载 则可以跳过它的更新 queue.sort(comparator) // ... // 先执行queue中的job 然后执行pendingPostFlushCbs中的job // 这里可以实现watch中的 postFlush try { for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { const job = queue[flushIndex] if (job && job.active !== false) { if (__DEV__ && check(job)) { continue } callWithErrorHandling(job, null, ErrorCodes.SCHEDULER) } } } finally { // job执行完毕后清空队列 flushIndex = 0 queue.length = 0
// in SSR there is no need to setup an actual effect, and it should be noop // unless it's eager or sync flush let ssrCleanup: (() =>void)[] | undefined if (__SSR__ && isInSSRComponentSetup) { // we will also not call the invalidate callback (+ runner is not set up) // ssr处理 }
// oldValue声明 多个source监听则初始化为数组 let oldValue: any = isMultiSource ? newArray((source as []).length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE // 调度器调用时执行 const job: SchedulerJob = () => { if (!effect.active || !effect.dirty) { return } if (cb) { // watch(source, cb) // 获取newValue const newValue = effect.run() if ( deep || forceTrigger || (isMultiSource ? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i])) : hasChanged(newValue, oldValue)) || (__COMPAT__ && isArray(newValue) && isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)) ) { // cleanup before running cb again if (cleanup) { cleanup() } // 调用cb 参数为newValue、oldValue、onCleanup callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ newValue, // pass undefined as the old value when it's changed for the first time oldValue === INITIAL_WATCHER_VALUE ? undefined : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue, onCleanup, ]) // 更新oldValue oldValue = newValue } } else { // watchEffect effect.run() } }
// important: mark the job as a watcher callback so that scheduler knows // it is allowed to self-trigger (#1727) job.allowRecurse = !!cb
// 创建任务队列的调度回调scheduler let scheduler: EffectScheduler if (flush === 'sync') { // 同步更新 即每次响应式数据改变都会回调一次cb scheduler = job as any // the scheduler function gets called directly } elseif (flush === 'post') { // job放入pendingPostFlushCbs队列中 // pendingPostFlushCbs队列会在queue队列执行完毕后执行 函数更新effect通常会放在queue队列中 // 所以pendingPostFlushCbs队列执行时组件已经更新完毕 scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { // default: 'pre' job.pre = true if (instance) job.id = instance.uid // 默认更新 // 原理在这:https://rile14929.github.io/zh-CN/vue3%E7%BB%84%E4%BB%B6%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0%E5%92%8CNextTick%E7%9A%84%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%88%B6.html scheduler = () => queueJob(job) }
// 创建effect effect.run的时候建立effect与getter内响应式数据的关系 const effect = new ReactiveEffect(getter, NOOP, scheduler)
get(target: Target, key: string | symbol, receiver: object) { const isReadonly = this._isReadonly, shallow = this._shallow if (key === ReactiveFlags.IS_REACTIVE) { // 代理 __v_isReactive return !isReadonly } elseif (key === ReactiveFlags.IS_READONLY) { // 代理 __v_isReadonly return isReadonly } elseif (key === ReactiveFlags.IS_SHALLOW) { // 代理 __v_isShallow return shallow } elseif (key === ReactiveFlags.RAW) { // 函数中判断响应式对象是否存在 __v_raw 属性,如果存在就返回这个响应式对象本身。 if ( receiver === (isReadonly ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap ).get(target) || // receiver is not the reactive proxy, but has the same prototype // this means the reciever is a user proxy of the reactive proxy Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver) ) { return target } // early return undefined return }
// 是否是数组 const targetIsArray = isArray(target)
if (!isReadonly) { // arrayInstrumentations 包含对数组一些方法修改的函数 if (targetIsArray && hasOwn(arrayInstrumentations, key)) { returnReflect.get(arrayInstrumentations, key, receiver) } if (key === 'hasOwnProperty') { return hasOwnProperty } }
const res = Reflect.get(target, key, receiver) // 内置 Symbol key 不需要依赖收集 if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { return res }
// 依赖收集 if (!isReadonly) { track(target, TrackOpTypes.GET, key) }
if (shallow) { return res }
if (isRef(res)) { // ref unwrapping - skip unwrap for Array + integer key. return targetIsArray && isIntegerKey(key) ? res : res.value }
// 如果 res 是个对象或者数组类型,则递归执行 reactive 函数把 res 变成响应式 if (isObject(res)) { // Convert returned value into a proxy as well. we do the isObject check // here to avoid invalid value warning. Also need to lazy access readonly // and reactive here to avoid circular dependency. return isReadonly ? readonly(res) : reactive(res) }
functioncreateArrayInstrumentations() { const instrumentations: Record<string, Function> = {} // instrument identity-sensitive Array methods to account for possible reactive // values ;(['includes', 'indexOf', 'lastIndexOf'] asconst).forEach(key => { instrumentations[key] = function (this: unknown[], ...args: unknown[]) { // toRaw 可以把响应式对象转成原始数据,this就是Reflect的receiver const arr = toRaw(this) as any for (let i = 0, l = this.length; i < l; i++) { // 依赖收集 track(arr, TrackOpTypes.GET, i + '') } // we run the method using the original args first (which may be reactive) const res = arr[key](...args) if (res === -1 || res === false) { // if that didn't work, run it again using raw values. return arr[key](...args.map(toRaw)) } else { return res } } }) // instrument length-altering mutation methods to avoid length being tracked // which leads to infinite loops in some cases (#2137) ;(['push', 'pop', 'shift', 'unshift', 'splice'] asconst).forEach(key => { instrumentations[key] = function (this: unknown[], ...args: unknown[]) { pauseTracking() pauseScheduling() const res = (toRaw(this) as any)[key].apply(this, args) resetScheduling() resetTracking() return res } }) return instrumentations }
也就是说,当 target 是一个数组的时候,我们去访问 target.includes、target.indexOf、 target.lastIndexOf 就会执行 arrayInstrumentations 代理的函数,除了调用数组本身的方法求值外,还对数组每个元素做了依赖收集。因为一旦数组的元素被修改,数组的这几个 API 的返回结果都可能发生变化,所以我们需要跟踪数组每个元素的变化。
// 创建要执行的deps数组 let deps: (Dep | undefined)[] = [] if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target // 清空数组或者map的时候触发所有key对应的的effect函数 deps = [...depsMap.values()] } elseif (key === 'length' && isArray(target)) { const newLength = Number(newValue) depsMap.forEach((dep, key) => { if (key === 'length' || (!isSymbol(key) && key >= newLength)) { deps.push(dep) } }) } else { // schedule runs for SET | ADD | DELETE // set add delete操作 将key对应的effect函数添加到deps数组中 if (key !== void0) { deps.push(depsMap.get(key)) }
// also run for iteration key on ADD | DELETE | Map.SET // 根据不同的操作类型push对应的dep switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } elseif (isIntegerKey(key)) { // new index added to array -> length changes deps.push(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) } break } }
pauseScheduling() for (const dep of deps) { if (dep) { triggerEffects( dep, DirtyLevels.Dirty, __DEV__ ? { target, type, key, newValue, oldValue, oldTarget, } : void0, ) } } resetScheduling() }