React 16.8.0 初次渲染 源码分析(二)
书接上文,从legacyRenderSubtreeIntoContainer - unbatchedUpdates看起
// Initial mount should not be batched. unbatchedUpdates(() => { // parentComponent = null if (parentComponent != null) { root.legacy_renderSubtreeIntoContainer( parentComponent, children, callback, ); } else { root.render(children, callback); } });
- packages/react-reconciler/src/ReactFiberScheduler.js
- 全局变量
- nextFlushedRoot
- nextFlushedExpirationTime
- currentSchedulerTime
- currentRendererTime
// TODO: Batching should be implemented at the renderer level, not inside // the reconciler. function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R { if (isBatchingUpdates && !isUnbatchingUpdates) { isUnbatchingUpdates = true; try { return fn(a); } finally { isUnbatchingUpdates = false; } } return fn(a); }
t.render
export function updateContainer( element: ReactNodeList, // <List />, container: OpaqueRoot, // FiberRoot parentComponent: ?React$Component<any, any>, // null callback: ?Function, // this._onCommit.bind(this) ): ExpirationTime { const current = container.current; // FiberNode const currentTime = requestCurrentTime(); const expirationTime = computeExpirationForFiber(currentTime, current); return updateContainerAtExpirationTime( element, container, parentComponent, expirationTime, callback, ); }
function requestCurrentTime() { // requestCurrentTime计算过期时间。 // 过期时间是通过将当前时间(开始时间)相加来计算的。 // 但是,如果在同一事件中安排了两个更新,则应将它们的开始时间视为同时进行,即使实际时钟时间在第一次和第二次调用之间提前。 // 换句话说,因为过期时间决定了更新的批处理方式, // 我们希望在同一事件中发生的所有具有相同优先级的更新接收相同的过期时间。否则我们会被撕裂。 // 我们跟踪两个不同的时间:当前“渲染器”时间和当前“调度程序”时间。渲染器时间可以随时更新;它的存在只是为了最小化调用性能。 // 但是,只有在没有挂起的工作,或者我们确定自己没有处于事件的中间时,调度程序时间才能被更新。 if (isRendering) { // We're already rendering. Return the most recently read time. return currentSchedulerTime; } // Check if there's pending work. findHighestPriorityRoot(); if ( nextFlushedExpirationTime === NoWork || nextFlushedExpirationTime === Never ) { // If there's no pending work, or if the pending work is offscreen, we can // read the current time without risk of tearing. // 如果没有挂起的工作,或者挂起的工作在屏幕外,我们可以读取当前时间而不会有撕裂的风险。 recomputeCurrentRendererTime(); currentSchedulerTime = currentRendererTime; return currentSchedulerTime; } // There's already pending work. We might be in the middle of a browser // event. If we were to read the current time, it could cause multiple updates // within the same event to receive different expiration times, leading to // tearing. Return the last read time. During the next idle callback, the // time will be updated. // 已经有工作要做了。我们可能正在浏览器事件中。 // 如果我们要读取当前时间,它可能会导致同一事件中的多个更新接收到不同的过期时间,从而导致撕裂。 // 返回上次读取时间。在下一次空闲回调期间,时间将被更新。 return currentSchedulerTime; }
function findHighestPriorityRoot() { let highestPriorityWork = NoWork; let highestPriorityRoot = null; // lastScheduledRoot = null if (lastScheduledRoot !== null) { let previousScheduledRoot = lastScheduledRoot; let root = firstScheduledRoot; while (root !== null) { const remainingExpirationTime = root.expirationTime; if (remainingExpirationTime === NoWork) { // This root no longer has work. Remove it from the scheduler. // TODO: This check is redudant, but Flow is confused by the branch // below where we set lastScheduledRoot to null, even though we break // from the loop right after. invariant( previousScheduledRoot !== null && lastScheduledRoot !== null, 'Should have a previous and last root. This error is likely ' + 'caused by a bug in React. Please file an issue.', ); if (root === root.nextScheduledRoot) { // This is the only root in the list. root.nextScheduledRoot = null; firstScheduledRoot = lastScheduledRoot = null; break; } else if (root === firstScheduledRoot) { // This is the first root in the list. const next = root.nextScheduledRoot; firstScheduledRoot = next; lastScheduledRoot.nextScheduledRoot = next; root.nextScheduledRoot = null; } else if (root === lastScheduledRoot) { // This is the last root in the list. lastScheduledRoot = previousScheduledRoot; lastScheduledRoot.nextScheduledRoot = firstScheduledRoot; root.nextScheduledRoot = null; break; } else { previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot; root.nextScheduledRoot = null; } root = previousScheduledRoot.nextScheduledRoot; } else { if (remainingExpirationTime > highestPriorityWork) { // Update the priority, if it's higher highestPriorityWork = remainingExpirationTime; highestPriorityRoot = root; } if (root === lastScheduledRoot) { break; } if (highestPriorityWork === Sync) { // Sync is highest priority by definition so // we can stop searching. break; } previousScheduledRoot = root; root = root.nextScheduledRoot; } } } nextFlushedRoot = highestPriorityRoot; nextFlushedExpirationTime = highestPriorityWork; }
function recomputeCurrentRendererTime() { const currentTimeMs = now() - originalStartTimeMs; currentRendererTime = msToExpirationTime(currentTimeMs); } const UNIT_SIZE = 10; const MAX_SIGNED_31_BIT_INT = 1073741823; const MAGIC_NUMBER_OFFSET = MAX_SIGNED_31_BIT_INT - 1; // 1 unit of expiration time represents 10ms. export function msToExpirationTime(ms: number): ExpirationTime { // Always add an offset so that we don't clash with the magic number for NoWork. return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0); }
computeExpirationForFiber返回一个Sync
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) { let expirationTime; if (expirationContext !== NoWork) { // An explicit expiration context was set; expirationTime = expirationContext; } else if (isWorking) { if (isCommitting) { // Updates that occur during the commit phase should have sync priority // by default. expirationTime = Sync; } else { // Updates during the render phase should expire at the same time as // the work that is being rendered. expirationTime = nextRenderExpirationTime; } } else { // No explicit expiration context was set, and we're not currently // performing work. Calculate a new expiration time. if (fiber.mode & ConcurrentMode) { if (isBatchingInteractiveUpdates) { // This is an interactive update expirationTime = computeInteractiveExpiration(currentTime); } else { // This is an async update expirationTime = computeAsyncExpiration(currentTime); } // If we're in the middle of rendering a tree, do not update at the same // expiration time that is already rendering. if (nextRoot !== null && expirationTime === nextRenderExpirationTime) { expirationTime -= 1; } } else { // This is a sync update expirationTime = Sync; } } if (isBatchingInteractiveUpdates) { // This is an interactive update. Keep track of the lowest pending // interactive expiration time. This allows us to synchronously flush // all interactive updates when needed. if ( lowestPriorityPendingInteractiveExpirationTime === NoWork || expirationTime < lowestPriorityPendingInteractiveExpirationTime ) { lowestPriorityPendingInteractiveExpirationTime = expirationTime; } } return expirationTime; }
export function updateContainerAtExpirationTime( element: ReactNodeList,// <List />, container: OpaqueRoot,// FiberRoot parentComponent: ?React$Component<any, any>,// null expirationTime: ExpirationTime, // Sync callback: ?Function, // this._onCommit.bind(this) ) { // TODO: If this is a nested container, this won't be the root. const current = container.current; // FiberNode const context = getContextForSubtree(parentComponent); // context = {} // container.context = null if (container.context === null) { container.context = context; // container.context = {} } else { container.pendingContext = context; } return scheduleRootUpdate(current, element, expirationTime, callback); }
- packages/react-reconciler/src/ReactUpdateQueue.js
export function createUpdate(expirationTime: ExpirationTime): Update<*> { return { expirationTime: expirationTime, tag: UpdateState, // 0 payload: null, callback: null, next: null, nextEffect: null, }; }
function scheduleRootUpdate( current: Fiber, // FiberNode element: ReactNodeList, // <List /> expirationTime: ExpirationTime, // Sync callback: ?Function, // this._onCommit.bind(this) ) { const update = createUpdate(expirationTime); // Caution: React DevTools currently depends on this property // being called "element". update.payload = {element}; callback = callback === undefined ? null : callback; if (callback !== null) { warningWithoutStack( typeof callback === 'function', 'render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback, ); update.callback = callback; } flushPassiveEffects(); enqueueUpdate(current, update); scheduleWork(current, expirationTime); return expirationTime; }
function flushPassiveEffects() { if (passiveEffectCallbackHandle !== null) { cancelPassiveEffects(passiveEffectCallbackHandle); } if (passiveEffectCallback !== null) { // We call the scheduled callback instead of commitPassiveEffects directly // to ensure tracing works correctly. passiveEffectCallback(); } }
- packages/react-reconciler/src/ReactUpdateQueue.js
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) { // Update queues are created lazily. const alternate = fiber.alternate; let queue1; let queue2; if (alternate === null) { // There's only one fiber. queue1 = fiber.updateQueue; queue2 = null; if (queue1 === null) { queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); } } else { // There are two owners. queue1 = fiber.updateQueue; queue2 = alternate.updateQueue; if (queue1 === null) { if (queue2 === null) { // Neither fiber has an update queue. Create new ones. queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); queue2 = alternate.updateQueue = createUpdateQueue( alternate.memoizedState, ); } else { // Only one fiber has an update queue. Clone to create a new one. queue1 = fiber.updateQueue = cloneUpdateQueue(queue2); } } else { if (queue2 === null) { // Only one fiber has an update queue. Clone to create a new one. queue2 = alternate.updateQueue = cloneUpdateQueue(queue1); } else { // Both owners have an update queue. } } } if (queue2 === null || queue1 === queue2) { // There's only a single queue. appendUpdateToQueue(queue1, update); } else { // There are two queues. We need to append the update to both queues, // while accounting for the persistent structure of the list — we don't // want the same update to be added multiple times. if (queue1.lastUpdate === null || queue2.lastUpdate === null) { // One of the queues is not empty. We must add the update to both queues. appendUpdateToQueue(queue1, update); appendUpdateToQueue(queue2, update); } else { // Both queues are non-empty. The last update is the same in both lists, // because of structural sharing. So, only append to one of the lists. appendUpdateToQueue(queue1, update); // But we still need to update the `lastUpdate` pointer of queue2. queue2.lastUpdate = update; } } }
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> { const queue: UpdateQueue<State> = { baseState, firstUpdate: null, lastUpdate: null, firstCapturedUpdate: null, lastCapturedUpdate: null, firstEffect: null, lastEffect: null, firstCapturedEffect: null, lastCapturedEffect: null, }; return queue; }
function appendUpdateToQueue<State>( queue: UpdateQueue<State>, update: Update<State>, ) { // Append the update to the end of the list. if (queue.lastUpdate === null) { // Queue is empty queue.firstUpdate = queue.lastUpdate = update; } else { queue.lastUpdate.next = update; queue.lastUpdate = update; } }
暂时将 scheduleWork 跳过
小结
- ReactFiberScheduler.js 定义了
- 全局变量
- nextFlushedRoot 下一次渲染root
- nextFlushedExpirationTime 下一次渲染过期时间
- currentSchedulerTime 当前计划时间
- currentRendererTime 当前渲染时间
- FiberRoot 下的 context 属性 被赋值 上下文空对象
- FiberRoot 下的 current: FiberNode 对象 几个属性都被赋值