# 阅读图解React笔记2
Table of Contents
本文仅仅是阅读 图解 React 原理系列 的笔记,了解更多内容请查看原文链接。
reconciler 运作流程的笔记。
概览
react-reconciler 主要的作用有 4 个方面:
- 输入:暴露 api 函数(如 scheduleUpdateOnFiber),供给其他包调用。
- 注册调度任务:与调度中心(scheduler)交互,注册调度任务 task,等待任务回调。
- 执行任务回调:在内存中构造出 fiber 树,同时与渲染器交互,在内存中创建出与 fiber 对应的 DOM 节点。
- 输出:与渲染器交互,渲染 DOM 节点。
主要功能代码集中于 react-reconciler/src/ReactFiberWorkLoop.new.js(v18.2.0),可以用图示表示:
输入
在 ReactFiberWorkLoop.js 中,承接输入函数的只有 scheduleUpdateOnFiber。在 react-reconciler 对外暴露的 api 函数中,只要涉及到需要改变 fiber 的操作,最后都会间接调用 scheduleUpdateOnFiber。
// 唯一接收输入信号的函数export function scheduleUpdateOnFiber( root: FiberRoot, fiber: Fiber, lane: Lane, eventTime: number) { if ((executionContext & RenderContext) !== NoLanes && root === workInProgressRoot) { //... } else { //... ensureRootIsScheduled(root, eventTime); //... }}注册调度任务
与输入环节紧密相连,scheduleUpdateOnFiber 函数之后,立即进入 ensureRootIsScheduled 函数。
// 该函数为 root 注册一个调度任务,在 root 下只能存在一个任务。// 如果有任务已经调度,会检测确保现有的任务的优先级与 root 的下一个任务优先级相同。// 该函数在每次更新和退出任务之前都会被调用。function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { // 前半部分:判断是否需要注册新的调度 const existingCallbackNode = root.callbackNode;
// 检测是否存在有通道(lanes)还在等待,如果有,将其标记为过期,以便接下来处理。 markStarvedLanesAsExpired(root, currentTime);
// 判断下个通道(lanes)的执行和优先级 const nextLanes = getNextLanes( root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes );
if (nextLanes === NoLanes) { if (existingCallbackNode !== null) { cancelCallback(existingCallbackNode); } root.callbackNode = null; root.callbackPriority = NoLanes; return; }
// 使用最高的优先级通道代表回调的优先级 const newCallbackPriority = getHighestPriorityLane(nextLanes);
// 检查是否存在已有的任务 const existingCallbackPriority = root.callbackPriority; if (existingCallbackPriority === newCallbackPriority) { return; }
if (existingCallbackNode !== null) { cancelCallback(existingCallbackNode); }
// 后半部分:注册一个新的调度任务 let newCallbackNode; if (newCallbackPriority === SyncLane) { if (root.tag === LegacyRoot) { scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root)); } else { scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root)); } if (supportsMicrotasks) { scheduleMicrotask(() => { flushSyncCallbacks(); }); } else { scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks); } newCallbackNode = null; } else { let schedulerPriorityLevel; switch (lanesToEventPriority(nextLanes)) { case DiscreteEventPriority: schedulerPriorityLevel = ImmediateSchedulerPriority; break; case ContinuousEventPriority: schedulerPriorityLevel = NormalSchedulerPriority; break; case IdleEventPriority: schedulerPriorityLevel = IdleEventPriority; break; default: schedulerPriorityLevel = NormalSchedulerPriority; break; } newCallbackNode = scheduleCallback( schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root) ); }
root.callbackPriority = newCallbackPriority; root.callbackNode = newCallbackNode;}ensureRootIsScheduled 的逻辑分为 2 部分:
- 前半部分:判断是否需要注册新的调度(如果无需新的调度,退出函数)
- 后半部分:注册调度任务
- performSyncWorkOnRoot 和 performConcurrentWorkOnRoot 都封装到了任务回调(scheduleCallback)中
- 等待调度中心执行任务,任务其实就是执行 performSyncWorkOnRoot 或 performConcurrentWorkOnRoot
执行任务回调
performSyncWorkOnRoot 的逻辑分为 3 部分:
- fiber 树构建
- 异常处理:有可能 fiber 树构建过程中出现异常
- 调用输出
// 不经过 scheduler 的同步任务入口function performSyncWorkOnRoot(root) { flushPassiveEffects();
// 1. fiber 树构建 let lanes = getNextLanes(root, NoLanes); if (!includesSomeLane(lanes, SyncLane)) { ensureRootIsScheduled(root, now()); return null; }
let exitStatus = renderRootSync(root, lanes);
// 2. 异常处理的:有可能 fiber 构建过程中实现异常 if (root.tag !== LegacyRoot && exitStatus === RootErrored) { //... }
// 3. 渲染 Fiber 树 const finishedWork: Fiber = (root.current.alternate: any); root.finishedWork = finishedWork; root.finishedLanes = lanes; commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions);
// 退出之前,确保下一个回调调度处于 pending 状态。 ensureRootIsScheduled(root, now()); return null;}performConcurrentWorkOnRoot 的逻辑与 performSyncWorkOnRoot 的不同之处在于,对于可中断渲染的支持:
- 调用 performConcurrentWorkOnRoot 函数时,首先检查是否处于渲染过程中,是否需要恢复上一次渲染。
- 如果本次渲染被中断,最后返回一个新的 performConcurrentWorkOnRoot 函数,等待下一次调用。
// 这是每一个并发任务的入口。function performConcurrentWorkOnRoot(root) { // 1. 刷新 pending 状态的被动副作用,以防它们安排额外的工作。 const originalCallbackNode = root.callbackNode; const didFlushPassiveEffects = flushPassiveEffects(); if (didFlushPassiveEffects) { if (root.callbackNode !== originalCallbackNode) { return null; } else { // 当前任务不会被取消,继续 } }
// 2. 获取本次渲染的优先级 let lanes = getNextLanes( root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes ); if (lanes === NoLanes) { return null; }
// 构建 fiber 树 let exitStatus = renderRootConcurrent(root, lanes); if (exitStatus !== RootInProgress) { if (exitStatus === RootErrored) { //... } if (exitStatus === RootFatalErrored) { //... } if (exitStatus === RootDidNotComplete) { markRootSuspended(root, lanes); } else { const renderWasConcurrent = !includesBlockingLane(root, lanes); const finishedWork: Fiber = (root.current.alternate: any); root.finishedWork = finishedWork; root.finishedLanes = lanes; // 渲染 fiber 树 finishConcurrentRender(root, exitStatus, lanes); } }
ensureRootIsScheduled(root, now());
if (root.callbackNode === originalCallbackNode) { // 渲染被阻断,返回一个新的 performConcurrentWorkOnRoot 函数,等待下一次调用 return performConcurrentWorkOnRoot.bind(null, root); } return null;}输出
在输出阶段,commitRoot 的实现逻辑在 commitRootImpl 函数上,其主要逻辑是处理副作用队列,将最新的 fiber 树结构反映到 DOM 上。
function commitRootImpl( root: FiberRoot, recoverableErrors: null | Array<CapturedValue<mixed>>, transtions: Array<Transition> | null, renderPriorityLevel: EventPriority) { // 设置局部变量 const finishedWork = root.finishedWork; const lanes = root.finishedLanes;
// 清空 FiberRoot 对象上的属性 root.finishedWork = null; root.finishedLanes = NoLanes; root.callbackNode = null;
// 提交阶段 let firstEffect = finishedWork.firstEffect; if (firstEffect !== null) { const prevExecutionContext = executionContext; executionContext |= CommitContext; // 阶段1: dom突变之前 nextEffect = firstEffect; do { commitBeforeMutationEffects(); } while (nextEffect !== null);
// 阶段2: dom突变, 界面发生改变 nextEffect = firstEffect; do { commitMutationEffects(root, renderPriorityLevel); } while (nextEffect !== null); root.current = finishedWork;
// 阶段3: layout阶段, 调用生命周期componentDidUpdate和回调函数等 nextEffect = firstEffect; do { commitLayoutEffects(root, lanes); } while (nextEffect !== null); nextEffect = null; executionContext = prevExecutionContext; } ensureRootIsScheduled(root, now()); return null;}核心逻辑分为 3 个步骤:
-
commitBeforeMutationEffects
- dom 变更之前,主要处理副作用队列中带有 Snapshot, Passive 标记的 fiber 节点。
-
commitMutationEffects
- dom 变更,界面得到更新,主要处理副作用队列中带有 Placement, Update, Deletion, Hydrating 标记的 fiber 节点。
-
commitLayoutEffects
- dom 变更后,主要处理副作用队列中带有 Update, Callback 标记的 fiber 节点。