主题
React 18 渲染流程
以 React 18 源码为线索,梳理一次更新从
createRoot().render()到 DOM 提交的完整链路:渲染入口 → 优先级体系 → 调度流程 → 同步/并发渲染 → Fiber 执行单元 → commit 阶段。代码摘自packages/react-dom、packages/react-reconciler与packages/scheduler,省略号处为与主线无关的实现。
目录
一、渲染入口
React 17 通过 ReactDOM.render 渲染项目,React 18 改为用 createRoot 创建应用入口:
tsx
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement,
);
root.render(<App />);1. createRoot
packages/react-dom/src/client/ReactDOMRoot.js
js
export function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
// ...
// 创建根节点
const root = createContainer(
container,
ConcurrentRoot, // 新版本的根节点,值为 1
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
);
const rootContainerElement: Document | Element | DocumentFragment =
container.nodeType === COMMENT_NODE
? (container.parentNode: any)
: container;
// 绑定 React 合成事件
listenToAllSupportedEvents(rootContainerElement);
// 返回根节点,其中会通过原型链增加 render 等函数
return new ReactDOMRoot(root);
}2. ReactDOMRoot
packages/react-dom/src/client/ReactDOMRoot.js
js
function ReactDOMRoot(internalRoot: FiberRoot) {
this._internalRoot = internalRoot;
}
// 使用 createRoot 创建根节点后,会使用 render 渲染根组件
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function (
children: ReactNodeList,
): void {
const root = this._internalRoot;
if (root === null) {
throw new Error('Cannot update an unmounted root.');
}
// 是 React 应用渲染和更新流程的入口点
updateContainer(children, root, null, null);
};3. updateContainer
packages/react-reconciler/src/ReactFiberReconciler.old.js
js
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
// ...
// 将更新加入队列
const root = enqueueUpdate(current, update, lane);
if (root !== null) {
// 触发调度
scheduleUpdateOnFiber(root, current, lane, eventTime);
// 关联过渡更新
entangleTransitions(root, current, lane);
}
return lane;
}4. scheduleUpdateOnFiber
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
调度的入口点,内部调用 ensureRootIsScheduled:
js
export function scheduleUpdateOnFiber(
root: FiberRoot,
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
// 检查是否存在嵌套更新,存在则报错提示
checkForNestedUpdates();
// 标记根节点有一个待处理的更新,记录更新的车道和时间戳
markRootUpdated(root, lane, eventTime);
// 检查是否在渲染过程中触发更新,常因为错误写法
if (
(executionContext & RenderContext) !== NoLanes &&
root === workInProgressRoot
) {
// 这些更新会被特殊处理,不会立即执行,而是被记录下来
workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
workInProgressRootRenderPhaseUpdatedLanes,
lane,
);
} else {
// 核心调度函数
ensureRootIsScheduled(root, eventTime);
}
}二、React 优先级
在介绍 ensureRootIsScheduled 之前,需要先了解 React 的三套优先级概念:调度任务的优先级、车道的优先级、事件优先级。
1. 调度任务的优先级
packages/scheduler/src/SchedulerPriorities.js
Scheduler 调度任务的优先级分为 5 种:
js
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;ImmediatePriority:离散事件,如 click、keydown、input。UserBlockingPriority:连续事件,如 scroll、drag、mouseover。- 其余是默认优先级
NormalPriority、低优先级LowPriority、空闲优先级IdlePriority。
2. 车道的优先级
packages/react-reconciler/src/ReactFiberLane.old.js
React 有自己的一套优先级机制,共有 31 种车道优先级。

3. 事件优先级
packages/react-reconciler/src/ReactEventPriorities.old.js
共有四种事件优先级:DiscreteEventPriority 离散事件优先级、ContinuousEventPriority 连续事件优先级、DefaultEventPriority 默认事件优先级、IdleEventPriority 空闲时间优先级:
js
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const IdleEventPriority: EventPriority = IdleLane;DiscreteEventPriority 离散事件优先级的值为 SyncLane,即同步车道。
packages/react-dom/src/events/ReactDOMEventListener.js
在 getEventPriority 函数中,会根据不同的事件分配优先级:离散事件 click 通过 switch 语句对应到 DiscreteEventPriority,即同步车道;对于 message 类型,则根据 Scheduler 调度任务的优先级匹配对应的事件优先级,即对应到车道优先级:
js
case 'message': {
// 可能处于 Scheduler 回调中
const schedulerPriority = getCurrentSchedulerPriorityLevel();
switch (schedulerPriority) {
case ImmediateSchedulerPriority:
return DiscreteEventPriority;
case UserBlockingSchedulerPriority:
return ContinuousEventPriority;
case NormalSchedulerPriority:
case LowSchedulerPriority:
// 低优先级暂按默认事件优先级处理,可能与 hydration 使用同一条车道
return DefaultEventPriority;
case IdleSchedulerPriority:
return IdleEventPriority;
default:
return DefaultEventPriority;
}
}事件优先级与调度优先级可以通过车道相互转换。
三、调度流程
1. ensureRootIsScheduled
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
ensureRootIsScheduled 决定了这次是同步调度还是并发调度。
什么时候是同步车道 SyncLane?
- 离散事件。
- 非并发模式,即用
ReactDOM.render()创建的 Legacy 模式应用。 - 错误边界处理,React 会用
SyncLane确保错误处理立即执行。 flushSync。
同步任务调用 performSyncWorkOnRoot,并发任务调用 performConcurrentWorkOnRoot:
js
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
const existingCallbackNode = root.callbackNode;
// 1. 检查是否有车道被饿死,标记为过期
markStarvedLanesAsExpired(root, currentTime);
// 2. 确定下一个要工作的车道
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
if (nextLanes === NoLanes) {
// 没有需要处理的车道
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
}
root.callbackNode = null;
root.callbackPriority = NoLane;
return;
}
// 3. 返回其中最高优先级的车道或车道组
const newCallbackPriority = getHighestPriorityLane(nextLanes);
// 4. 检查是否需要重新调度任务
const existingCallbackPriority = root.callbackPriority;
if (
existingCallbackPriority === newCallbackPriority &&
// act 的特殊处理:如果当前调度的是 Scheduler 任务而不是 act 任务,
// 需要取消并重新调度到 act 队列
!(
__DEV__ &&
ReactCurrentActQueue.current !== null &&
existingCallbackNode !== fakeActCallbackNode
)
) {
if (__DEV__) {
// 复用已有任务时它必须存在;离散更新的微任务不可取消,值为 null
if (
existingCallbackNode == null &&
existingCallbackPriority !== SyncLane
) {
console.error(
'Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.',
);
}
}
// 优先级没有变化,复用已有任务直接返回
return;
}
if (existingCallbackNode != null) {
// 5. 取消现有的任务
cancelCallback(existingCallbackNode);
}
// 调度新的回调
let newCallbackNode;
// 6. 同步任务调度,newCallbackPriority 是 nextLanes 中的最高优先级
if (newCallbackPriority === SyncLane) {
// 同步回调会被放入一个内部队列
if (root.tag === LegacyRoot) {
if (__DEV__ && ReactCurrentActQueue.isBatchingLegacy !== null) {
ReactCurrentActQueue.didScheduleLegacyUpdate = true;
}
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
} else {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
}
// 微任务或立即任务调度
if (supportsMicrotasks) {
if (__DEV__ && ReactCurrentActQueue.current !== null) {
// 在 act 内部使用 act 队列,保证在当前作用域结束时统一刷新
ReactCurrentActQueue.current.push(flushSyncCallbacks);
} else {
scheduleMicrotask(() => {
// Safari 中追加 iframe 会强制微任务执行,
// 而 React 不支持在 render 或 commit 中途执行回调,这里要排除这两种情况
if (
(executionContext & (RenderContext | CommitContext)) ===
NoContext
) {
flushSyncCallbacks();
}
});
}
} else {
// 用立即任务刷新队列
scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
}
newCallbackNode = null;
} else {
// 并发任务调度:先把事件优先级转换为 Scheduler 优先级
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority: // 离散事件优先级
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority: // 连续事件优先级
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority: // 默认事件优先级
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority: // 空闲事件优先级
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
// 调度并发任务
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
// 并发任务的回调函数
performConcurrentWorkOnRoot.bind(null, root),
);
}
// 7. 更新根节点的回调优先级和节点
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}提示:点击这类离散事件是同步车道,即同步任务,会调用
performSyncWorkOnRoot处理。SyncLane同步车道的更新是同步更新,其余都是并发更新。
2. scheduleSyncCallback
把回调函数 performSyncWorkOnRoot 添加到数组 syncQueue 中,并在下一个微任务中依次调用 flushSyncCallbacks,或在 flushSync 函数结束时刷新:
js
export function scheduleSyncCallback(callback: SchedulerCallback) {
// 把回调推入内部队列,要么在下一个 tick 刷新,
// 要么在 flushSyncCallbackQueue 被调用时更早刷新
if (syncQueue === null) {
syncQueue = [callback];
} else {
// 直接推入已有队列,无需再次调度:队列创建时已经调度过一次
syncQueue.push(callback);
}
}提示:微任务使用
queueMicrotask,不支持时降级为Promise.then,再不支持则降级为setTimeout。除了在微任务中处理同步队列,也可以手动调用flushSyncAPI:它会先执行传入的回调函数,再调用flushSyncCallbacks执行syncQueue中的回调,从而强制 React 在回调内同步刷新更新,确保 DOM 立即更新。
3. scheduleCallback
scheduleCallback 即 scheduler 包中的 unstable_scheduleCallback。
它负责创建调度任务、设置过期时间:过期任务加入 taskQueue,未过期任务加入 timerQueue;未过期的以开始时间作为排序依据,已过期的以过期时间作为排序依据。浏览器有空闲时间时,Scheduler 调用 flushWork 处理任务队列,flushWork 再调用 workLoop 依次取出 taskQueue 的第一条执行:
js
function unstable_scheduleCallback(priorityLevel, callback, options) {
// 获取当前时间
var currentTime = getCurrentTime();
// 任务开始时间
var startTime;
if (typeof options === "object" && options !== null) {
// 判断用户是否传入了自定义的延迟时间
var delay = options.delay;
if (typeof delay === "number" && delay > 0) {
// 有延迟时间,任务开始时间就是当前时间加上延迟时间
startTime = currentTime + delay;
} else {
// 没有延迟时间,任务需要立刻开始
startTime = currentTime;
}
} else {
startTime = currentTime;
}
// 计算间隔时间
var timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT; // -1
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT; // 250
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT; // 1073741823
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT; // 10000
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT; // 5000
break;
}
// 过期时间 = 任务开始时间 + timeout
// 立即执行的优先级(ImmediatePriority)timeout 为 -1,意味着立刻就过期
var expirationTime = startTime + timeout;
// 创建调度任务
var newTask = {
id: taskIdCounter++,
// 真正的任务函数
callback,
// 任务优先级
priorityLevel,
// 任务开始的时间,表示任务何时才能执行
startTime,
// 任务的过期时间
expirationTime,
// 在小顶堆队列中排序的依据
sortIndex: -1,
};
if (startTime > currentTime) {
// 任务未过期,以开始时间作为 timerQueue 排序的依据
newTask.sortIndex = startTime;
// 加入 timerQueue 队列
push(timerQueue, newTask);
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
// taskQueue 中没有任务,且当前任务是 timerQueue 中排名最靠前的一个,
// 说明需要检查 timerQueue 中有没有可以转移到 taskQueue 的任务
if (isHostTimeoutScheduled) {
// 即将调度新的 requestHostTimeout,先取消掉旧的
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
// 调用 requestHostTimeout 实现任务转移,开启调度
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
// 任务已经过期,以过期时间作为 taskQueue 排序的依据
newTask.sortIndex = expirationTime;
// 加入 taskQueue 队列
push(taskQueue, newTask);
// 开始执行任务,使用 flushWork 去执行 taskQueue
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
}堆操作与定时任务的相关方法:
pop:类似于数组的pop()方法。peek:获取队列的第一项,与pop的区别是只获取不删除,类似于数组的arr[0]。push:向队列尾部加入一项,类似于数组的push()方法。requestHostTimeout:一个定时器任务,时间间隔为「任务的开始时间 - 当前时间」,即剩余等待时间。handleTimeout:调用advanceTimers,检查timerQueue中过期的任务并放入taskQueue。
4. requestHostCallback
packages/scheduler/src/forks/Scheduler.js
js
function requestHostCallback(callback) {
// 过期任务请求调度
scheduledHostCallback = callback;
// 判断是否有 messageChannel 在运行
if (!isMessageLoopRunning) {
// 初始为 false
isMessageLoopRunning = true;
schedulePerformWorkUntilDeadline();
}
}5. schedulePerformWorkUntilDeadline
通过 postMessage 主动向浏览器发送一条消息,收到消息说明浏览器空闲:
js
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = () => {
port.postMessage(null);
};6. performWorkUntilDeadline
判断 scheduledHostCallback 是否有值,有值则执行;执行完发现还有更多任务,就再次调用 schedulePerformWorkUntilDeadline 向浏览器发送消息判断是否空闲,直到任务被完全执行完:
js
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// 记录开始时间,用于衡量主线程被阻塞的时长
startTime = currentTime;
const hasTimeRemaining = true;
let hasMoreWork = true;
try {
hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
} finally {
if (hasMoreWork) {
// 还有任务,就在上一条消息结束时再调度下一条消息事件
schedulePerformWorkUntilDeadline();
} else {
isMessageLoopRunning = false;
scheduledHostCallback = null;
}
}
} else {
isMessageLoopRunning = false;
}
// 让出主线程给浏览器绘制后,重置这个标记
needsPaint = false;
};scheduledHostCallback 在 requestHostCallback 中被赋值,最终指向 unstable_scheduleCallback 中传入的 flushWork,也就是 ensureRootIsScheduled 里 scheduleCallback 的回调。
7. flushWork
启动工作循环,处理所有待执行的任务:
js
try {
return workLoop(hasTimeRemaining, initialTime);
} catch (error) {
if (currentTask !== null) {
const currentTime = getCurrentTime();
markTaskErrored(currentTask, currentTime);
currentTask.isQueued = false;
}
throw error;
}8. workLoop
从任务队列 taskQueue 中取出任务,在 while 循环中处理每个任务,并且在每个任务开始之前检查是否应该中断。如果中断,performWorkUntilDeadline 中的 hasMoreWork 为 true,会再次去检查浏览器是否空闲:
js
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
advanceTimers(currentTime);
currentTask = peek(taskQueue);
while (
currentTask !== null &&
!(enableSchedulerDebugging && isSchedulerPaused)
) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
// 当前任务尚未过期,但已经到达时间片上限,跳出循环让出主线程
break;
}
const callback = currentTask.callback;
if (typeof callback === "function") {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
if (enableProfiling) {
markTaskRun(currentTask, currentTime);
}
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === "function") {
// 回调返回了新函数,说明任务还没做完,下次继续
currentTask.callback = continuationCallback;
if (enableProfiling) {
markTaskYield(currentTask, currentTime);
}
} else {
if (enableProfiling) {
markTaskCompleted(currentTask, currentTime);
currentTask.isQueued = false;
}
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
advanceTimers(currentTime);
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
// 返回是否还有额外任务
if (currentTask !== null) {
return true;
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}9. cancelCallback
ensureRootIsScheduled 中还有 cancelCallback 取消任务的操作。取消场景是:当前正在执行一个较低优先级的任务时,来了更高优先级的任务,需要先取消低优先级任务,再调度一个任务优先执行高优先级任务。
取消的方式是把任务的 callback 置为 null:
js
function unstable_cancelCallback(task) {
if (enableProfiling) {
if (task.isQueued) {
const currentTime = getCurrentTime();
markTaskCanceled(task, currentTime);
task.isQueued = false;
}
}
// 把 callback 置空以标记任务被取消;不能从队列中直接移除任意节点,
// 因为基于数组的小顶堆只能移除堆顶元素
task.callback = null;
}workLoop 中会判断 callback 的类型,是函数则执行,否则直接把任务弹出。
四、同步调度
1. performSyncWorkOnRoot
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
performSyncWorkOnRoot 是同步渲染的入口:先执行 render 阶段把 vdom 转成 fiber,再执行 commit 阶段更新到 DOM。
js
function performSyncWorkOnRoot(root) {
// ...
exitStatus = renderRootSync(root, lanes);
// ...
commitRoot(root);
return null;
}2. renderRootSync
renderRootSync 是同步模式的 render 入口:
- 不断地尝试执行
workLoopSync; - 如果
workLoopSync执行成功,则跳出循环; - 如果执行过程中出现异常,则捕获后交由
handleError处理。
js
function renderRootSync(root: FiberRoot, lanes: Lanes) {
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
// 创建 workInProgress
prepareFreshStack(root, lanes);
startWorkOnPendingInteractions(root, lanes);
}
do {
try {
// 工作轮询
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
}3. workLoopSync
只要 workInProgress 不为空,就执行 performUnitOfWork。performUnitOfWork 负责处理单个 Fiber 节点,无论同步调度还是并发调度,都会走到该函数:
js
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}五、并发调度
1. performConcurrentWorkOnRoot
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
performConcurrentWorkOnRoot 是并发渲染的入口,负责根据任务优先级和时间分片策略执行渲染任务:通过判断是否存在堵塞车道、过期车道以及是否超时,来决定是否开启时间分片。
js
function performConcurrentWorkOnRoot(root, didTimeout) {
// includesBlockingLane(root, lanes):包含堵塞车道则不启用时间分片
// includesExpiredLane(root, lanes):包含过期车道则不启用时间分片,防止饥饿问题
// (disableSchedulerTimeoutInWorkLoop || !didTimeout):禁用调度超时或没有超时,才启用时间分片
const shouldTimeSlice =
!includesBlockingLane(root, lanes) &&
!includesExpiredLane(root, lanes) &&
(disableSchedulerTimeoutInWorkLoop || !didTimeout);
// 根据 shouldTimeSlice 的值决定渲染模式
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes) // 并发渲染
: renderRootSync(root, lanes); // 同步渲染
// ...
// render 完成,进入 commit 阶段
finishConcurrentRender(root, exitStatus, lanes);
}如果 shouldTimeSlice 为 false,就调用 renderRootSync 进行同步 render。
提示:点击这类离散事件会被分配为同步车道,在
ensureRootIsScheduled中即被判定为同步更新,并在下一个微任务中执行;setTimeout中的更新会被分配为默认车道,通过scheduleCallback调度performConcurrentWorkOnRoot,但由于是堵塞车道,shouldTimeSlice为false,最终会在浏览器空闲时使用renderRootSync同步渲染。默认车道的过期时间是 5s,浏览器是否空闲每 5ms 判断一次。
在 React 18 中,只有明确标记为并发、且不包含堵塞车道的更新,才会使用 renderRootConcurrent 进行并发渲染。
2. renderRootConcurrent
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
renderRootConcurrent 是并发模式的 render 入口,通过 workLoopConcurrent 执行可中断的渲染工作:
js
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
// 上下文管理
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher();
// 2. 工作栈重置条件判断:根节点有变化或车道有更新,
// 丢弃现有栈重新准备一份,否则从上次中断处继续
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
workInProgressTransitions = getTransitionsForLanes(root, lanes);
resetRenderTimer();
prepareFreshStack(root, lanes); // 核心重置方法
}
if (enableSchedulingProfiler) {
markRenderStarted(lanes);
}
do {
try {
// 驱动 Fiber 处理
workLoopConcurrent();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// 5. 清理阶段,上下文重置
resetContextDependencies();
popDispatcher(prevDispatcher);
executionContext = prevExecutionContext;
if (workInProgress !== null) {
// 还有剩余工作,未渲染完成
if (enableSchedulingProfiler) {
markRenderYielded();
}
return RootInProgress;
} else {
// 整棵树处理完成
if (enableSchedulingProfiler) {
markRenderStopped();
}
// 置空以标记当前没有进行中的 render
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
// 返回渲染最终状态
return workInProgressRootExitStatus;
}
}3. workLoopConcurrent
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
如果 workInProgress 未渲染完成且不需要打断,就使用 performUnitOfWork 处理单个 Fiber 节点:
js
function workLoopConcurrent() {
// 在未超时且存在待处理 Fiber 时持续工作
// 通过 shouldYield() 与 Scheduler 协作,当浏览器需要处理高优先级任务时暂停工作
while (workInProgress !== null && !shouldYield()) {
// 每次循环执行 performUnitOfWork 处理单个 Fiber 节点
performUnitOfWork(workInProgress);
}
}提示:最终还是和 React 17 一样使用
performUnitOfWork处理单个 Fiber 节点,只是多了一个shouldYield()判断是否中断。
shouldYield 就是 scheduler 文件中的 shouldYieldToHost 函数。
4. shouldYield
packages/scheduler/src/forks/SchedulerPostTask.js
shouldYield 实际上是 unstable_shouldYield,判断当前时间是否超过了 deadline:
js
export function unstable_shouldYield() {
return getCurrentTime() >= deadline;
}deadline 的值在同文件的 runTask 中被赋值,每次调用都会根据当前时间加 5ms:
js
deadline = getCurrentTime() + yieldInterval; // yieldInterval 为 5即 React 18 处理并发任务时,会定期每 5ms 主动让出主线程的控制权。
优先级的高低只影响 Scheduler 中 taskQueue 的排序结果,是否打断只取决于过期时间。
六、执行单元
1. performUnitOfWork
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
performUnitOfWork 位于 Render 阶段的中间层,是工作循环函数调用的核心处理单元,用于处理单个 Fiber 节点。
它会调用 beginWork 处理当前 Fiber 节点并计算下一个节点:如果没有下一个节点,说明处理完成,调用 completeUnitOfWork;如果有下一个节点,把 next 赋值给 workInProgress,由 workLoopConcurrent 或 workLoopSync 中的 while 循环继续处理。
js
function performUnitOfWork(unitOfWork: Fiber): void {
// ...
next = beginWork(current, unitOfWork, subtreeRenderLanes);
if (next === null) {
// 没有新的 work,完成当前工作
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}2. beginWork
packages/react-reconciler/src/ReactFiberBeginWork.old.js
负责把 vdom 转成 fiber,不同节点的 reconcile 逻辑不同:函数组件调用 updateFunctionComponent,class 组件调用 updateClassComponent。
js
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// ...
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,
);
}
}3. updateFunctionComponent
packages/react-reconciler/src/ReactFiberBeginWork.old.js
函数组件会被调用,拿到 render 出的 vdom 后继续做 reconcile:
js
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderLanes,
) {
// 构建 hooks
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
// 生成 Fiber 链表
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}4. renderWithHooks
渲染 Hooks 的流程可参考 V16.13.1 的实现。
5. reconcileChildren
packages/react-reconciler/src/ReactFiberBeginWork.old.js
current 指当前渲染的树,通过判断 current 的值可知是否需要 diff:
js
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
}6. ChildReconciler
packages/react-reconciler/src/ReactChildFiber.old.js
mountChildFibers 和 reconcileChildFibers 都是 ChildReconciler 的返回值,只是传参不同:
js
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);ChildReconciler 负责把 vdom 转成 Fiber,并进行 diff 操作:
js
function ChildReconciler(shouldTrackSideEffects) {
// ...
// 负责把 vdom 转成 Fiber
function createChild() {}
// 函数入口
function reconcileChildFibers() {}
return reconcileChildFibers;
}performUnitOfWork 会判断其返回值是否为 null:为 null 说明执行完毕,调用 completeUnitOfWork,render 阶段结束;不为 null 则把返回值赋给 workInProgress,等待下一次处理。
提示:同步任务中
workLoopSync会执行完所有performUnitOfWork,即 render 阶段不可打断;并发任务中workLoopConcurrent是可被打断的,会在浏览器空闲时再次执行。
七、commit 阶段
1. finishConcurrentRender
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
与 performSyncWorkOnRoot 相比,performConcurrentWorkOnRoot 在 commit 阶段会多调用一个 finishConcurrentRender,因为新增了乐观更新功能,可以回退 UI。
finishConcurrentRender 根据 exitStatus 的不同状态执行不同逻辑:
RootErrored:渲染过程中发生错误,直接调用commitRoot提交错误状态。RootSuspended:渲染被挂起(例如等待数据加载),某些条件下延迟调用commitRoot,或立即调用commitRoot提交回退 UI(乐观更新)。RootSuspendedWithDelay:与RootSuspended类似,但会根据延迟时间决定是否提交回退 UI(乐观更新)。RootCompleted:渲染完成,调用commitRoot提交最终的 UI 更新。
js
function finishConcurrentRender(root, exitStatus, lanes) {
switch (exitStatus) {
// ...
case RootCompleted: {
// 工作已完成,可以提交
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
break;
}
default: {
throw new Error("Unknown root exit status.");
}
}
}2. commitRoot
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
setCurrentUpdatePriority(DiscreteEventPriority) 中 DiscreteEventPriority 的值为 SyncLane,即把当前更新任务设置为同步任务。这表明 commitRoot 总是同步完成,不会被中断。
js
function commitRoot(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
) {
// 1. 保存当前优先级和过渡状态
const previousUpdateLanePriority = getCurrentUpdatePriority();
const prevTransition = ReactCurrentBatchConfig.transition;
try {
// 2. 设置提交阶段的优先级和过渡状态
ReactCurrentBatchConfig.transition = null;
setCurrentUpdatePriority(DiscreteEventPriority);
// 3. 执行实际的提交工作
commitRootImpl(
root,
recoverableErrors,
transitions,
previousUpdateLanePriority,
);
} finally {
// 4. 恢复之前的优先级和过渡状态
ReactCurrentBatchConfig.transition = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority);
}
return null;
}3. commitRootImpl
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
commit 也分为三个小阶段:Before Mutation 阶段、Mutation 阶段、Layout 阶段。
- before mutation:在 DOM 操作之前执行。
- mutation 阶段:调用
commitMutationEffects,根据 Fiber 节点的类型和副作用标志执行相应的 DOM 操作。 - layout 阶段:调用
commitLayoutEffects,在 DOM 变更后执行布局相关的副作用,ref 的更新与useLayoutEffect的回调都在此阶段执行。
提示:
useEffect的回调在 commit 阶段完成、浏览器绘制之后异步执行。
js
// 1. Before Mutation 阶段
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
root,
finishedWork,
);
// 2. Mutation 阶段
commitMutationEffects(root, finishedWork, lanes);
// 3. Layout 阶段
commitLayoutEffects(finishedWork, root, lanes);
// 请求绘制
requestPaint();
// 处理剩余被中断的低优先级更新,安排下一次渲染
ensureRootIsScheduled();提示:
requestPaint会把needsPaint置为true,shouldYieldToHost中据此判断需要让出主线程。即如果有新的渲染任务,React 会打断当前流程,把主线程让给浏览器进行绘制。