于是,我们在第二次执行 cal 时,就需要提前判断入参。如果我们发现,两次的执行入参是一样的,那么我们就可以不必重新运算,而是直接返回上一次的运算结果
代码调整如下
functioncul(a, b) { // 对比之后选择复用缓存的结果 if (cache.preA === a && cache.preB === b) { return cache.preResult } // 缓存参数与结果 cache.preA = a cache.preB = b const result = expensive(a, b) cache.preResult = result return result }
/** @noinline */ functionworkLoopConcurrent() { // Perform work until Scheduler asks us to yield while (workInProgress !== null && !shouldYield()) { // $FlowFixMe[incompatible-call] found when upgrading Flow performUnitOfWork(workInProgress); } }
workLoopSync 的逻辑非常简单,就是开始对 Fiber 节点进行遍历。
// The work loop is an extremely hot path. Tell Closure not to inline it. /** @noinline */ functionworkLoopSync() { // Perform work without checking if we need to yield between fiber. while (workInProgress !== null) { performUnitOfWork(workInProgress); } }
functionperformUnitOfWork(unitOfWork: Fiber): void{ 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 { next = beginWork(current, unitOfWork, subtreeRenderLanes); }
resetCurrentDebugFiberInDEV(); unitOfWork.memoizedProps = unitOfWork.pendingProps; if (next === null) { // If this doesn't spawn new work, complete the current work. completeUnitOfWork(unitOfWork); } else { workInProgress = next; }
ReactCurrentOwner.current = null; }
我们要把 current 与 workInProgress 理解成为一个一直会移动的指针,他们总是指向当前正在执行的 Fiber 节点。当前节点执行完之后,我们就会在修改 workInProgress 的值
核心的代码是下面这两句
next = beginWork(current, unitOfWork, subtreeRenderLanes); workInProgress = next;
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 ( oldProps !== newProps || hasLegacyContextChanged() || // Force a re-render if the implementation changed due to hot reload: (__DEV__ ? workInProgress.type !== current.type : false) ) { ... }
functioncheckScheduledUpdateOrContext( current: Fiber, renderLanes: Lanes, ): boolean{ // Before performing an early bailout, we must check if there are pending // updates or context. const updateLanes = current.lanes; if (includesSomeLane(updateLanes, renderLanes)) { returntrue; } // No pending update, but because context is propagated lazily, we need // to check for a context change before we bail out. if (enableLazyContextPropagation) { const dependencies = current.dependencies; if (dependencies !== null && checkIfContextChanged(dependencies)) { returntrue; } } returnfalse; }
这里很难理解的地方在于 state 的比较是如何发生的。简单说一下,当我们在刚开始调用 dispatchReducerAction 等函数触发更新时,都会提前给被影响的 fiber 节点标记更新优先级。然后再通过 scheduleUpdateOnFiber 进入后续的调度更新流程。
例如这样
functiondispatchReducerAction<S, A>( fiber: Fiber, queue: UpdateQueue<S, A>, action: A, ): void{ ... const lane = requestUpdateLane(fiber); ... scheduleUpdateOnFiber(root, fiber, lane);
functionupdateReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init?: I => S, ): [S, Dispatch<A>] { const hook = updateWorkInProgressHook(); return updateReducerImpl(hook, ((currentHook: any): Hook), reducer); }
functionupdateReducerImpl<S, A>( hook: Hook, current: Hook, reducer: (S, A) => S, ): [S, Dispatch<A>] { const queue = hook.queue; ... // Mark that the fiber performed work, but only if the new state is // different from the current state. if (!is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate(); } ... }
functionreconcileSingleElement( returnFiber: Fiber, currentFirstChild: Fiber | null, element: ReactElement, lanes: Lanes, ): Fiber{ const key = element.key; let child = currentFirstChild; while (child !== null) { // TODO: If key === null and child.key === null, then this only applies to // the first item in the list. if (child.key === key) { const elementType = element.type; if (elementType === REACT_FRAGMENT_TYPE) { if (child.tag === Fragment) { deleteRemainingChildren(returnFiber, child.sibling); const existing = useFiber(child, element.props.children); existing.return = returnFiber; if (__DEV__) { existing._debugSource = element._source; existing._debugOwner = element._owner; } return existing; } } else { if ( child.elementType === elementType || // Keep this check inline so it only runs on the false path: (__DEV__ ? isCompatibleFamilyForHotReloading(child, element) : false) || // Lazy types should reconcile their resolved type. // We need to do this after the Hot Reloading check above, // because hot reloading has different semantics than prod because // it doesn't resuspend. So we can't let the call below suspend. (typeof elementType === 'object' && elementType !== null && elementType.$$typeof === REACT_LAZY_TYPE && resolveLazy(elementType) === child.type) ) { deleteRemainingChildren(returnFiber, child.sibling); const existing = useFiber(child, element.props); existing.ref = coerceRef(returnFiber, child, element); existing.return = returnFiber; if (__DEV__) { existing._debugSource = element._source; existing._debugOwner = element._owner; } return existing; } } // Didn't match. deleteRemainingChildren(returnFiber, child); break; } else { deleteChild(returnFiber, child); } child = child.sibling; }