阅读图解React笔记2
2022 react本文仅仅是阅读 图解 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 节点。