import React, { useState, useEffect } from 'react';
import * as ReactDOM from 'react-dom/client';
const Counter = () => {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
useEffect(() => {
console.log("effect...start", count);
return () => {
console.log("effect...end", count);
};
}, [count]);
return (
<button onClick={handleIncrement}>{count}</button>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);
createRoot
该函数只做了一件事:创建两个fiber:FiberRootNode(整个应用的根)、FiberNode(root对应的fiber)。
调用createFiberRoot创建root,该fiber的构造函数为FiberRootNode
function FiberRootNode(
containerInfo, // 容器信息,例如 DOM 节点
tag, // 标签,表示根节点类型,如 ConcurrentRoot 或 LegacyRoot
hydrate, // 是否是服务端渲染的 hydrate 模式
identifierPrefix, // 标识符前缀,主要用于生成 DOM 元素的唯一标识
onRecoverableError // 当遇到可恢复错误时的回调函数
) {
this.tag = tag; // 存储根节点类型, **ConcurrentRoot: 1**
this.containerInfo = containerInfo; // 存储与根节点关联的容器信息(DOM 或其他容器)
this.pendingChildren = null; // 用于存储挂起的子节点
this.current = null; // 指向当前的 Fiber 树(workInProgress Fiber)
this.pingCache = null; // 用于存储在 ping 时恢复的工作
this.finishedWork = null; // 存储render事情的Fiber树,在commit阶段会用到
this.timeoutHandle = noTimeout; // 超时处理的句柄,默认值为 noTimeout
this.context = null; // 存储与根节点关联的上下文信息
this.pendingContext = null; // 挂起的上下文信息
this.callbackNode = null; // 调度中的回调节点
this.callbackPriority = NoLane; // 当前回调的优先级,默认为 NoLane
this.eventTimes = createLaneMap(NoLanes); // 存储每条车道的事件时间
this.expirationTimes = createLaneMap(NoTimestamp); // 每条车道的过期时间
// 调度相关的 lane 状态,用于跟踪 Fiber 树的更新优先级
this.pendingLanes = NoLanes; // 挂起的 lanes,表示哪些工作正在等待执行
this.suspendedLanes = NoLanes; // 被挂起的 lanes
this.pingedLanes = NoLanes; // 已被 ping 的 lanes,表示需要被唤醒的任务
this.expiredLanes = NoLanes; // 已过期的 lanes,需要尽快执行
this.mutableReadLanes = NoLanes; // 进行可变读取的 lanes
this.finishedLanes = NoLanes; // 已完成的 lanes,表示已处理完的更新
this.entangledLanes = NoLanes; // 表示 lanes 被纠缠(entangled),影响优先级调度
this.entanglements = createLaneMap(NoLanes); // 用于记录 lanes 的纠缠关系
...
}
调用createHostRootFiber创建根fiber,该fiber的构造函数是FiberNode
// 定义 FiberNode 类的构造函数,用于创建 React 的 Fiber 节点
function FiberNode(
tag: WorkTag, // 表示 Fiber 节点的类型(如 FunctionComponent、ClassComponent)
pendingProps: mixed, // 此 Fiber 对应组件传入的最新属性(props)
key: null | string, // 用于列表渲染中标识唯一性的 key
mode: TypeOfMode // 当前 Fiber 节点的模式,如 ConcurrentMode 或 NoMode
) {
// 组件实例相关的字段
this.tag = tag; // 节点的类型(如 HostComponent, ClassComponent 等)
this.key = key; // key 属性,用于优化列表渲染
this.elementType = null; // JSX 元素的类型(如函数组件的函数本身)
this.type = null; // 组件的具体类型,可能是类或函数(与 elementType 类似)
this.stateNode = null; // 与此 Fiber 节点关联的实际 DOM 或类实例
// Fiber 树结构中的关系
this.return = null; // 指向父 Fiber
this.child = null; // 指向子 Fiber
this.sibling = null; // 指向兄弟 Fiber
this.index = 0; // 该 Fiber 在父节点中的位置索引
this.ref = null; // 保存对 DOM 或类实例的引用,便于 ref 回调函数
// 与更新相关的字段
this.pendingProps = pendingProps; // 最新的 props
this.memoizedProps = null; // 已保存的上一次渲染的 props
this.updateQueue = null; // 更新队列,包含待处理的更新
this.memoizedState = null; // 已保存的上一次渲染的 state
this.dependencies = null; // 与此 Fiber 相关的上下文依赖
this.mode = mode; // 当前 Fiber 所处的模式,如同步或异步模式
// 副作用相关的字段
this.flags = NoFlags; // 当前节点的副作用标志位,指示哪些操作需要执行(如更新、删除)
this.subtreeFlags = NoFlags; // 子树的副作用标志位,指示整个子树中是否有需要处理的副作用
this.deletions = null; // 需要删除的 Fiber 子节点数组
// 优先级调度相关的字段
this.lanes = NoLanes; // 当前 Fiber 节点的更新优先级
this.childLanes = NoLanes; // 子树的更新优先级
// 双缓存(alternate)相关
this.alternate = null; // 用于指向当前 Fiber 的替代版本(即双缓存机制中的另一份 Fiber)
这两个fiber互有联系,以下称FiberRootNode为root,FiberNode为rootFiber。
root.current = rootFiber;
rootFiber.stateNode = root;
// root的dom上有属性指向其对应的rootFiber
export function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
node[internalContainerInstanceKey] = hostRoot;
}
render
updateContainer
element是传给render函数的组件根节点,是个Symbol(react.element),将创建的更新对象添加到根fiber的updateQueue中。
ensureRootIsScheduled
// 使用确定的调度优先级调度回调任务
// `scheduleCallback` 是一个函数,用于根据优先级调度回调任务
// `performConcurrentWorkOnRoot` 是实际要执行的工作,绑定了根节点 `root`,在任务调度时会调用
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
以上操作就是注册调度任务
Scheduler_scheduleCallback(priorityLevel, callback)
Scheduler
最小堆
调度器内部存储任务的两个队列,是个数组。逻辑上是最小堆,也就是二叉树。
// Tasks are stored on a min heap
var taskQueue = [];
var timerQueue = [];
最小堆,是一种经过排序的完全二叉树
1
/ \
3 5
/ \ /
6 8 7
每个父节点比子节点都要小,在此对应着父节点的过期时间比子节点都小,所以说第一个节点任务,过期时间最小,每次从该任务队列中取出第一个,就是取出过期时间最紧急的任务
// 消息队列的实现结构,类似这样
[1, 3, 5, 6, 8, 7]
Parent(i) = Math.floor((i - 1) / 2)
LeftChild(i) = 2 * i + 1
RightChild(i) = 2 * i + 2
unstable_scheduleCallback
- 创建任务,任务主要包括过期时间和回调,根据时间判断是同步任务还是异步任务,分别将其推入taskQueue、timerQueue
- 判断是否有任务阻塞,没有就启动任务刷新flushWork
function unstable_scheduleCallback(priorityLevel, callback, options) {
// 获取当前时间
var currentTime = getCurrentTime();
var startTime;
if (typeof options === 'object' && options !== null) {
// 从 options 中提取延迟时间
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
// 如果延迟时间是正数,则设置任务的开始时间为当前时间加上延迟时间
startTime = currentTime + delay;
} else {
// 否则,任务的开始时间为当前时间
startTime = currentTime;
}
} else {
// 如果 options 不是对象或为 null,任务的开始时间为当前时间
startTime = currentTime;
}
var timeout;
// 根据优先级选择任务超时时间
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
// 计算任务的过期时间
var expirationTime = startTime + timeout;
// 创建新的任务对象
var newTask = {
id: taskIdCounter++, // 任务的唯一标识符
callback, // 任务执行的回调函数
priorityLevel, // 任务的优先级
startTime, // 任务的开始时间
expirationTime, // 任务的过期时间
sortIndex: -1, // 用于排序的索引
};
if (enableProfiling) {
newTask.isQueued = false; // 如果启用了性能分析器,将任务标记为未排队
}
if (startTime > currentTime) {
// 如果任务是延迟任务
newTask.sortIndex = startTime; // 设置排序索引为开始时间
push(timerQueue, newTask); // 将任务推入定时队列
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
// 如果任务队列为空,且这是定时队列中最早的任务
if (isHostTimeoutScheduled) {
// 如果已经安排了超时,取消现有的超时
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
// 安排超时
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
// 如果任务不是延迟任务
newTask.sortIndex = expirationTime; // 设置排序索引为过期时间
push(taskQueue, newTask); // 将任务推入任务队列
if (enableProfiling) {
markTaskStart(newTask, currentTime); // 如果启用了性能分析器,标记任务开始时间
newTask.isQueued = true; // 标记任务为已排队
}
// 如果需要,安排主机回调
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
// 返回新创建的任务
return newTask;
}
注意以上操作,最终会执行回调 flushWork
requestHostCallback
function requestHostCallback(callback) {
// 把传入的回调函数存储到全局变量 `scheduledHostCallback` 中
scheduledHostCallback = callback;
// 如果消息循环还没有运行
if (!isMessageLoopRunning) {
// 设置 `isMessageLoopRunning` 为 `true`,表示消息循环正在运行
isMessageLoopRunning = true;
// 调度任务的执行,使用之前定义的 `schedulePerformWorkUntilDeadline`
// 该函数会根据不同的环境选择最优的方式来执行任务
schedulePerformWorkUntilDeadline();
}
}
scheduledHostCallback:这是一个全局变量,用于存储传入的回调函数。这个回调函数将在后续的调度过程中被调用
schedulePerformWorkUntilDeadline
触发该函数,会在下一个事件循环中执行回调
let schedulePerformWorkUntilDeadline; // 定义调度工作执行的函数
// 检查是否存在 `localSetImmediate`,这是 Node.js 和老版本的 IE 中的 API
if (typeof localSetImmediate === 'function') {
// Node.js 和旧版 IE
// 这里有几个原因我们优先使用 setImmediate。
// 1. 与 MessageChannel 不同,setImmediate 不会阻止 Node.js 进程退出。
// 即使这是 Scheduler 的 DOM 版本,你可能会在 Node.js 15+ 版本和 jsdom 的混合环境中运行。
// 相关 issue: https://github.com/facebook/react/issues/20756
//
// 2. setImmediate 会比 MessageChannel 更早运行任务,这是我们需要的语义。
// 如果其他浏览器实现了 setImmediate,使用它会更好。
// 3. 但是无论如何,这两者都不如原生的调度 API 优秀(如果原生调度 API 存在的话)。
// 定义 `schedulePerformWorkUntilDeadline`,使用 `localSetImmediate` 调度任务
schedulePerformWorkUntilDeadline = () => {
localSetImmediate(performWorkUntilDeadline); // 在下一次事件循环中执行 `performWorkUntilDeadline`
};
// 如果 `MessageChannel` 存在
} else if (typeof MessageChannel !== 'undefined') {
// DOM 和 Worker 环境下
// 我们更喜欢使用 MessageChannel,因为 setTimeout 有 4ms 的延迟(称为 "clamping")。
const channel = new MessageChannel(); // 创建一个新的 MessageChannel 对象
const port = channel.port2; // 使用 port2 作为发送端口
channel.port1.onmessage = performWorkUntilDeadline; // 当 port1 接收到消息时,执行 `performWorkUntilDeadline`
// 定义 `schedulePerformWorkUntilDeadline`,使用 `port.postMessage` 来调度任务
schedulePerformWorkUntilDeadline = () => {
console.log("port.postMessage..."); // 输出调试信息
port.postMessage(null); // 使用 `postMessage` 触发消息事件,进而触发 `performWorkUntilDeadline`
};
// 如果没有 `setImmediate` 或 `MessageChannel`
} else {
// 这种情况通常只在非浏览器环境下会发生。
// 定义 `schedulePerformWorkUntilDeadline`,使用 `localSetTimeout` 调度任务
schedulePerformWorkUntilDeadline = () => {
localSetTimeout(performWorkUntilDeadline, 0); // 使用 `setTimeout` 来尽快执行任务,延迟为 0ms
};
}
performWorkUntilDeadline
该函数会检查scheduledHostCallback是否有返回值,如果有说明taskQueue不为空,只是时间切片满了,余下的taskQueue任务会在下一次事件循环执行,因为触发了 schedulePerformWorkUntilDeadline
// 执行调度的任务直到时间截止
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 标志,表示浏览器不需要立即执行绘制操作
needsPaint = false;
};
flushWork
每次执行的scheduledHostCallback,其实就是在requestHostCallback中注册的flushWork,该函数用来执行 workLoop
function flushWork(hasTimeRemaining, initialTime) {
// 如果启用了性能分析
if (enableProfiling) {
// 记录调度器未被挂起的时间
markSchedulerUnsuspended(initialTime);
}
// 我们需要在下一次工作被调度时一个主机回调
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
// 如果之前调度了一个超时但现在不再需要,取消它
isHostTimeoutScheduled = false;
cancelHostTimeout();
}
// 标记开始执行工作
isPerformingWork = true;
// 保存当前的优先级级别
const previousPriorityLevel = currentPriorityLevel;
try {
if (enableProfiling) {
try {
// 执行工作循环
return workLoop(hasTimeRemaining, initialTime);
} catch (error) {
// 如果工作循环抛出错误
if (currentTask !== null) {
const currentTime = getCurrentTime();
// 记录任务出错的时间
markTaskErrored(currentTask, currentTime);
currentTask.isQueued = false; // 标记任务未排队
}
// 重新抛出错误
throw error;
}
} else {
// 在生产环境中没有错误捕获
return workLoop(hasTimeRemaining, initialTime);
}
} finally {
// 清理状态
currentTask = null; // 重置当前任务
currentPriorityLevel = previousPriorityLevel; // 恢复之前的优先级级别
isPerformingWork = false; // 标记工作执行完毕
if (enableProfiling) {
const currentTime = getCurrentTime();
// 记录调度器被挂起的时间
markSchedulerSuspended(currentTime);
}
}
}
workLoop
该函数主要用来执行任务队列中的回调,这里涉及到 时间切片,主要计算从performWorkUntilDeadline到现在的时间差,是否超过了 frameInterval,有个计算公式,根据浏览器的帧频率fps计算,若fps不存在默认 5ms
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) {
// 如果有更多任务,返回 true
return true;
} else {
// 如果没有更多任务,检查定时器队列中是否有任务
const firstTimer = peek(timerQueue);
// 如果有定时器任务,设置超时请求
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
// 返回 false,表示没有更多任务需要处理
return false;
}
}
performConcurrentWorkOnRoot
主要处理 React 16/17 以及之后的并发渲染逻辑
function performConcurrentWorkOnRoot(root, didTimeout) {
...
// 在某些情况下禁用时间切片:如果工作已经 CPU 绑定了太久(“过期”工作,以防止饿死),或者我们处于同步更新默认模式
// TODO: 我们仅仅防御性地检查 `didTimeout`,以考虑 Scheduler 中我们仍在调查的 bug。修复 Scheduler 中的 bug 后,可以移除此检查,因为我们自己跟踪过期
const shouldTimeSlice =
!includesBlockingLane(root, lanes) &&
!includesExpiredLane(root, lanes) &&
(disableSchedulerTimeoutInWorkLoop || !didTimeout);
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes) // 如果支持时间切片,则使用并发模式渲染根节点
: renderRootSync(root, lanes); // 否则,使用同步模式渲染根节点
if (exitStatus === RootDidNotComplete) {
// 渲染在没有完成树的情况下回溯
// 这种情况仅在并发渲染期间发生,而不是离散或同步更新
markRootSuspended(root, lanes);
} else {
// 渲染完成
// 检查此渲染是否可能让出给并发事件
// 如果是这样,确认任何新渲染的存储器是否一致
// TODO: 即使并发渲染可能从未让出主线程,如果它足够快,或者它过期了,也可以跳过一致性检查
const renderWasConcurrent = !includesBlockingLane(root, lanes);
const finishedWork = root.current.alternate;
if (
renderWasConcurrent &&
!isRenderConsistentWithExternalStores(finishedWork)
) {
// 在交错事件中存储器被更改。再渲染一次,使用同步模式来阻止进一步的变更
exitStatus = renderRootSync(root, lanes);
// 再次检查是否出现了错误
if (exitStatus === RootErrored) {
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(root, errorRetryLanes);
// 假设树现在是一致的,因为我们没有让出给任何并发事件
}
}
if (exitStatus === RootFatalErrored) {
const fatalError = workInProgressRootFatalError;
prepareFreshStack(root, NoLanes);
markRootSuspended(root, lanes);
ensureRootIsScheduled(root, now());
throw fatalError;
}
}
// 现在我们有了一致的树。下一步是提交它,或者如果有挂起,则在超时后提交
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);
}
}
// 确保根节点被调度
ensureRootIsScheduled(root, now());
// 如果当前执行的任务节点与原始任务节点相同,需要返回一个继续执行的任务
if (root.callbackNode === originalCallbackNode) {
// 继续执行当前任务
return performConcurrentWorkOnRoot.bind(null, root);
}
// 如果任务节点已更改或不再存在,则返回 null
return null;
}
渲染模式
renderRootSync
createWorkInProgress
创建新的 workInProgress(render过程最重要的tree)。该函数是 创建fiber树的开始,首次使用createFiber,先创建根fiber,其实根fiber在 createRoot 时候已经创建了,又创建一个新的根fiber,将原有根fiber属性复制过来
/**
* 该函数用于创建当前 Fiber 节点的 alternate(备用 fiber 节点),
* 用于在新的渲染周期中进行工作。
*
* @param current 当前正在工作的 Fiber 节点。
* @param pendingProps 传入的新的待处理属性。
* @returns 返回一个新的或者复用的 Fiber 节点。
*/
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
// 检查当前 Fiber 节点是否已经有一个备用节点
let workInProgress = current.alternate;
if (workInProgress === null) {
// 如果没有备用 Fiber 节点,创建一个新的
// 使用双缓冲技术来减少对象分配,保证最多有两份 Fiber 树
// 懒加载创建 alternate 以避免不必要的内存分配
// 这样当节点不更新时可以回收内存
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
// 复制现有 Fiber 节点的一些属性到备用节点上
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 互相引用 alternate,形成双向链接
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// 如果已经有一个备用 Fiber 节点,则复用它
workInProgress.pendingProps = pendingProps;
// 更新 type,Blocks 类型组件会存储数据在 type 上
workInProgress.type = current.type;
// 重置 effect 标记,因为已经有 alternate 节点
workInProgress.flags = NoFlags;
// 清除无效的子树效果
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
// 如果启用了 Profiler Timer,则重置时间相关的属性
if (enableProfilerTimer) {
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = -1;
}
}
// 重置所有非静态的效果标记
workInProgress.flags = current.flags & StaticMask;
// 复制 lanes 和 childLanes 用于调度优先级
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
// 复制 Fiber 节点的子节点、已记忆的属性和状态
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// 克隆依赖关系对象,防止渲染阶段修改时与当前 Fiber 共享
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
};
// 这些字段在父组件的协调过程中会被覆盖
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
// 如果启用了 Profiler Timer,复制一些基础持续时间数据
if (enableProfilerTimer) {
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
return workInProgress;
}
workLoopSync
同步执行整个 Fiber 树的渲染,不会中断或让步给浏览器的主线程。它一直处理 workInProgress,直到所有任务完成
function workLoopSync() {
// 已经超时,所以执行工作时不再检查是否需要让步给浏览器。
// React 在并发模式下会定期检查是否需要暂停渲染,以避免长时间占用主线程导致界面卡顿。
// 但在同步模式下(如这里的 `workLoopSync`),不需要做这种检查。
while (workInProgress !== null) {
// `workInProgress` 表示当前正在处理的 Fiber 节点。
// 调用 `performUnitOfWork` 来处理当前的 Fiber 节点的工作。
performUnitOfWork(workInProgress);
}
}
performUnitOfWork
function performUnitOfWork(unitOfWork: Fiber): void {
// 获取当前 Fiber 的备用状态(alternate)
// 理想情况下,其他代码不应依赖于此,但依赖于此可避免在工作进程中增加额外的字段
const current = unitOfWork.alternate;
// 设置当前调试 Fiber(仅在开发模式下)
setCurrentDebugFiberInDEV(unitOfWork);
let next;
// 如果启用性能分析器且 Fiber 的模式包含 ProfileMode
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
// 启动性能分析器计时器
startProfilerTimer(unitOfWork);
// 开始处理当前 Fiber 的工作
next = beginWork(current, unitOfWork, subtreeRenderLanes);
// 停止性能分析器计时器并记录时间差
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
// 不启用性能分析器,则直接处理当前 Fiber 的工作
next = beginWork(current, unitOfWork, subtreeRenderLanes);
}
// 重置调试 Fiber(仅在开发模式下)
resetCurrentDebugFiberInDEV();
// 更新当前 Fiber 的 memoizedProps
unitOfWork.memoizedProps = unitOfWork.pendingProps;
// 如果 `next` 为 null,表示没有新工作被生成,完成当前工作
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
// 否则,将 `workInProgress` 设置为下一个工作单元
workInProgress = next;
}
// 清空 ReactCurrentOwner
ReactCurrentOwner.current = null;
}
beginWork
function beginWork(current, workInProgress, renderLanes) {
// 处理当前 Fiber 节点的工作
switch (workInProgress.tag) {
case FunctionComponent:
// 处理函数组件
updateFunctionComponent(workInProgress, renderLanes);
break;
case ClassComponent:
// 处理类组件
updateClassComponent(current, workInProgress, renderLanes);
break;
case HostComponent:
// 处理宿主组件
updateHostComponent(current, workInProgress);
break;
// 处理其他类型的 Fiber 节点
// ...
}
// 处理子节点
reconcileChildren(current, workInProgress, renderLanes);
// 决定是否需要继续工作
return workInProgress.child;
}
workInProgress.child会作为新的workInProgress。接着performUnitOfWork,如果有子节点一直beginWork
HostRoot 根节点
// 根据传入的 React 元素创建一个新的 Fiber 节点
const created = createFiberFromElement(element, returnFiber.mode, lanes);
// 处理 `ref` 属性并将其设置到新创建的 Fiber 节点上
created.ref = coerceRef(returnFiber, currentFirstChild, element);
// 将新创建的 Fiber 节点的 `return` 属性设置为父级 Fiber 节点
created.return = returnFiber;
// 返回新创建的 Fiber 节点,以供后续使用
return created;
最后返回workInProgress.child,就函数组件fiber。
函数组件
mountIndeterminateComponent函数
let children = Component(props, secondArg);
createFiberFromElement
由于有原生标签,将其转为fiber
useState
function mountState<S>(
initialState: (() => S) | S, // `initialState` 可以是一个值或者是一个返回值的函数。
): [S, Dispatch<BasicStateAction<S>>] { // 返回一个数组,包含当前的状态和一个用于更新状态的 `dispatch` 函数。

const hook = mountWorkInProgressHook();
// 获取当前正在处理的 Hook。这是 React 内部的一部分,表示当前的 `useState` Hook 在工作链表中的位置。
if (typeof initialState === 'function') {
initialState = initialState();
}
// 将 `initialState` 存储在 `hook.memoizedState` 和 `hook.baseState` 中。
// `memoizedState` 是当前的状态值,`baseState` 是基础状态,用于计算新的状态。
hook.memoizedState = hook.baseState = initialState;
// 创建一个更新队列,这个队列存储了所有将要应用的状态更新。
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null, // `pending` 存储挂起的更新。初始化为 `null`,表示当前没有挂起的更新。
interleaved: null, // 用于存储跨组件的并发更新,初始化为 `null`。
lanes: NoLanes, // `lanes` 用于跟踪更新的优先级。
dispatch: null, // `dispatch` 是实际触发状态更新的函数,在稍后会初始化。
lastRenderedReducer: basicStateReducer, // 指向最后使用的状态更新函数,默认为 `basicStateReducer`。
lastRenderedState: (initialState: any), // 存储最后一次渲染时的状态,这里是初始状态。
};
// 将创建的队列与当前 Hook 关联起来,以便在后续的状态更新中使用。
hook.queue = queue;
// 定义 `dispatch` 函数,绑定到当前渲染的 Fiber 和更新队列上。
// `dispatchSetState` 是负责处理状态更新的函数,绑定之后的 `dispatch` 函数将在 `useState` 中返回。
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchSetState.bind(
null,
currentlyRenderingFiber, // 绑定当前正在渲染的 Fiber,这样在触发更新时,React 知道是哪个组件发起的。
queue, // 绑定更新队列,用于追踪和应用状态更新。
): any));
// 返回当前状态和 `dispatch` 函数,以便组件在后续可以通过 `dispatch` 更新状态。
return [hook.memoizedState, dispatch];
}
useEffect
前置行为与useState一致,都是先创建hook,再与上一个hook关联
function mountWorkInProgressHook(): Hook {
// 创建一个新的 Hook 对象,初始化它的状态和队列
const hook: Hook = {
memoizedState: null, // 用于存储 Hook 的状态值,最常见的是 `useState` 的状态
baseState: null, // 用于保存 Hook 的基本状态,通常用于处理某些优化
baseQueue: null, // 用于保存基础的更新队列
queue: null, // 这个 queue 是用来存放当前 Hook 的更新队列,例如 `useState` 里的 setState 触发的更新
next: null, // 指向下一个 Hook,形成一个链表结构
};
// 如果 `workInProgressHook` 为 null,表示这是当前 fiber 上第一个 hook
if (workInProgressHook === null) {
// 将这个新创建的 Hook 挂载到 `currentlyRenderingFiber` 的 `memoizedState` 上
// `currentlyRenderingFiber` 表示当前正在渲染的组件的 fiber 节点
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 如果已经有 hook 存在了,则把这个 hook 添加到链表末尾
// `workInProgressHook.next` 指向新创建的 hook
workInProgressHook = workInProgressHook.next = hook;
}
// 返回当前工作中的 hook
return workInProgressHook;
}
再将其挂到fiber的updateQueue和lastEffect
function pushEffect(tag, create, destroy, deps) {
// 创建一个 Effect 对象,包含标签(tag)、创建函数(create)、销毁函数(destroy)和依赖项(deps)
const effect: Effect = {
tag, // Effect 的类型标签,用于区分不同的副作用类型,比如 useEffect、useLayoutEffect 等
create, // 副作用的创建函数,也就是在渲染完成之后要执行的逻辑
destroy, // 用于清理副作用的销毁函数,在下次渲染之前或组件卸载时调用
deps, // 依赖项数组,用于决定副作用是否需要重新执行
next: (null: any), // 链表结构,用于将 Effect 连接起来形成一个循环链表
};
// 获取当前渲染的 Fiber 的 updateQueue,它用于保存 Function 组件的更新队列
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
// 如果当前 Fiber 没有 updateQueue,则创建一个新的 FunctionComponentUpdateQueue
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
// 初始化 Effect 链表,将当前 effect 作为唯一的节点,形成一个自循环链表
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 如果已经有更新队列,说明之前可能已经存在 effect 链表
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
// 如果更新队列中没有 lastEffect,则说明这是第一个 effect
// 初始化为一个自循环的链表
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 如果已经有 lastEffect,说明更新队列中已经有一些 effect 了
// 找到链表的第一个 effect
const firstEffect = lastEffect.next;
// 将新 effect 插入到链表的末尾,形成环状链表
lastEffect.next = effect;
effect.next = firstEffect;
// 更新 lastEffect,指向最新的 effect
componentUpdateQueue.lastEffect = effect;
}
}
// 返回创建的 effect 对象
return effect;
}
reconcileChildren
export function reconcileChildren(
current: Fiber | null, // 当前 fiber 节点
workInProgress: Fiber, // 正在处理的 fiber 节点
nextChildren: any, // 下一次渲染的子节点
renderLanes: Lanes, // 渲染优先级
) {
if (current === null) {
// 如果这是一个新的组件(即还没有渲染过),
// 我们不会通过应用最小副作用来更新其子节点。
// 相反,我们会在它渲染之前将所有子节点添加到该组件中。
// 这意味着我们可以通过不跟踪副作用来优化协调过程。
workInProgress.child = mountChildFibers(
workInProgress, // 当前正在工作的 fiber 节点
null, // 当前没有旧的子节点,因为这是首次渲染
nextChildren, // 新的子节点
renderLanes // 当前渲染的优先级
);
} else {
// 如果 `current` 存在,表示当前组件已经渲染过,接下来进行更新。
// 如果当前的子节点与 `workInProgress` 的子节点相同,意味着这些子节点
// 尚未开始处理,因此我们需要通过克隆算法来复制所有当前子节点。
// 如果在此之前有已经进行了一部分的工作,那部分工作在此时无效,
// 所以我们会将其抛弃,并重新进行协调。
workInProgress.child = reconcileChildFibers(
workInProgress, // 当前正在工作的 fiber 节点
current.child, // 上一次渲染的子节点
nextChildren, // 新的子节点
renderLanes // 当前渲染的优先级
);
}
}
reconcileChildFibers
function reconcileChildFibers(parentFiber, currentFirstChild, newChild, lanes) {
// 如果顶层是无键片段(<>...</>),则将其视为数组处理
if (newChild 是无键片段) {
newChild = newChild.props.children;
}
// 如果新子节点是对象类型
if (newChild 是对象) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
// 如果是 React 元素,调用 `reconcileSingleElement` 进行协调
return placeSingleChild(
reconcileSingleElement(parentFiber, currentFirstChild, newChild, lanes)
);
case REACT_PORTAL_TYPE:
// 如果是 Portal,调用 `reconcileSinglePortal` 进行协调
return placeSingleChild(
reconcileSinglePortal(parentFiber, currentFirstChild, newChild, lanes)
);
case REACT_LAZY_TYPE:
// 如果是懒加载组件,初始化后递归调用 `reconcileChildFibers`
return reconcileChildFibers(
parentFiber,
currentFirstChild,
newChild._init(newChild._payload),
lanes
);
}
// 如果是数组类型,调用 `reconcileChildrenArray` 进行协调
if (Array.isArray(newChild)) {
return reconcileChildrenArray(parentFiber, currentFirstChild, newChild, lanes);
}
// 如果是可迭代对象,调用 `reconcileChildrenIterator` 进行协调
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(parentFiber, currentFirstChild, newChild, lanes);
}
}
// 如果新子节点是文本节点(字符串或数字),调用 `reconcileSingleTextNode` 进行协调
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(parentFiber, currentFirstChild, String(newChild), lanes)
);
}
// 其他情况,视为无效子节点,删除剩余的旧子节点
return deleteRemainingChildren(parentFiber, currentFirstChild);
}
如果新子节点是一个 React 元素,调用reconcileSingleElement的createFiberFromElement,再打上标记
// 标记这个 fiber 为 Placement,以指示它需要被插入。
newFiber.flags |= Placement;
completeUnitOfWork
HostComponent
创建dom
export function createInstance(
type: string, // 元素类型,例如 'div' 或 'span'
props: Props, // 元素的属性
rootContainerInstance: Container, // 根容器实例
hostContext: HostContext, // 主机上下文
internalInstanceHandle: Object, // 内部实例句柄
): Instance {
let parentNamespace: string;
// 创建 DOM 元素
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
// 缓存 Fiber 节点到dom上
precacheFiberNode(internalInstanceHandle, domElement);
// 更新 Fiber 属性
updateFiberProps(domElement, props);
return domElement;
}
// 创建一个新的 DOM 实例
const instance = createInstance(
type, // 组件类型(如 'div', 'span')
newProps, // 组件的新属性(props)
rootContainerInstance, // 根容器实例,通常是页面的根 DOM 元素
currentHostContext, // 当前主机上下文,包含关于当前环境的信息
workInProgress // 当前正在处理的 Fiber 对象,用于关联新创建的 DOM 实例
);
// 将所有子元素附加到新创建的 DOM 实例上
appendAllChildren(instance, workInProgress, false, false);
// 参数解释:
// - instance: 新创建的 DOM 元素
// - workInProgress: 当前 Fiber 节点,包含子元素的信息
// 将新创建的 DOM 实例赋值给当前 Fiber 节点的 stateNode 属性
workInProgress.stateNode = instance;
经过该操作,fiber上的stateNode就有dom了
commitRoot
提交流程主要操作的是 finishedWork,对应render流程的 workInProgress
function commitRoot(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
) {
console.log("commitRoot...");
// 记录当前的更新优先级和过渡配置
const previousUpdateLanePriority = getCurrentUpdatePriority(); // 保存当前更新优先级
const prevTransition = ReactCurrentBatchConfig.transition; // 保存当前过渡配置
try {
// 在提交过程中,清除当前的过渡配置
ReactCurrentBatchConfig.transition = null;
// 设置当前更新的优先级为离散事件优先级
setCurrentUpdatePriority(DiscreteEventPriority);
// 调用实际的提交实现函数,处理根 Fiber 的提交
commitRootImpl(
root,
recoverableErrors,
transitions,
previousUpdateLanePriority,
);
} finally {
// 恢复之前保存的过渡配置
ReactCurrentBatchConfig.transition = prevTransition;
// 恢复之前保存的更新优先级
setCurrentUpdatePriority(previousUpdateLanePriority);
}
// 函数执行完毕,返回 null
return null;
}
再一个消息,触发浏览器事件循环
commitMutationEffects
commitMutationEffectsOnFiber
commitReconciliationEffects
function commitReconciliationEffects(finishedWork: Fiber) {
// 获取当前 fiber 的 effect flags,这些标志位记录了需要处理的副作用类型。
const flags = finishedWork.flags;
// 如果当前 fiber 上有 Placement 标志位,表示需要执行插入操作。
if (flags & Placement) {
try {
// 执行插入操作,将 fiber 插入到 DOM 中。
commitPlacement(finishedWork);
} catch (error) {
// 如果插入操作中发生错误,捕获并记录错误。
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
// 成功插入后,清除 Placement 标志位,表示当前 fiber 已经被插入到 DOM 中。
// TODO: 之前的实现依赖于这个标志位来判断元素是否已插入,但 isMounted 已被弃用,未来可能不再需要这个清除操作。
finishedWork.flags &= ~Placement;
}
// 如果当前 fiber 上有 Hydrating 标志位,表示需要执行 hydration 操作。
if (flags & Hydrating) {
// 清除 Hydrating 标志位,表示 hydration 操作已完成。
finishedWork.flags &= ~Hydrating;
}
}
commitPlacement
function commitPlacement(finishedWork: Fiber): void {
// 如果不支持 mutation 操作(即不支持直接修改 DOM),则不执行任何操作。
if (!supportsMutation) {
return;
}
// 获取当前 fiber 的父级 fiber,用于确定插入的位置。
const parentFiber = getHostParentFiber(finishedWork);
// Note: these two variables *must* always be updated together.
switch (parentFiber.tag) {
case HostComponent: {
// 如果父级 fiber 是一个 HostComponent,则获取它的实例。
const parent: Instance = parentFiber.stateNode;
// 如果父级 fiber 有 ContentReset 标志位,则重置文本内容。
if (parentFiber.flags & ContentReset) {
// 重置父级节点的文本内容,以便插入新内容。
resetTextContent(parent);
// 清除 ContentReset 标志位,表示文本内容已被重置。
parentFiber.flags &= ~ContentReset;
}
// 获取当前 fiber 的同级 sibling(兄弟节点)中的插入位置。
const before = getHostSibling(finishedWork);
// 递归插入当前 fiber 及其所有子节点到 DOM 中。
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
case HostRoot:
case HostPortal: {
// 如果父级 fiber 是 HostRoot 或 HostPortal,则获取它的容器信息。
const parent: Container = parentFiber.stateNode.containerInfo;
// 获取当前 fiber 的同级 sibling(兄弟节点)中的插入位置。
const before = getHostSibling(finishedWork);
// 递归插入当前 fiber 及其所有子节点到容器中。
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
break;
}
}
}
到此 页面上才有了交互变化
再次发送一个消息,通知浏览器事件循环
commitPassiveMountOnFiber
commitHookEffectListMount
// 该函数用于处理 React 中的 Hook 的挂载阶段的副作用(如 useEffect 或 useLayoutEffect)
// 根据传入的 flag 类型(如 HookPassive 或 HookLayout),来执行相应的副作用
function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
// 获取当前 fiber 的更新队列
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
// 如果有副作用链表,则执行挂载副作用
if (lastEffect !== null) {
// 获取第一个副作用
const firstEffect = lastEffect.next;
let effect = firstEffect;
// 循环遍历所有副作用,直到回到第一个副作用
do {
// 检查当前副作用是否与传入的 flags 匹配
if ((effect.tag & flags) === flags) {
// 开启性能分析器时,会标记副作用挂载的开始与结束
if (enableSchedulingProfiler) {
if ((flags & HookPassive) !== NoHookEffect) {
// 标记被动副作用的挂载开始
markComponentPassiveEffectMountStarted(finishedWork);
} else if ((flags & HookLayout) !== NoHookEffect) {
// 标记布局副作用的挂载开始
markComponentLayoutEffectMountStarted(finishedWork);
}
}
// 挂载阶段,调用副作用的 create 函数,通常是 useEffect 的回调
const create = effect.create;
// 执行 create 函数并将其返回值(销毁函数)保存到 effect.destroy 中
effect.destroy = create();
// 结束性能分析器的标记
if (enableSchedulingProfiler) {
if ((flags & HookPassive) !== NoHookEffect) {
markComponentPassiveEffectMountStopped();
} else if ((flags & HookLayout) !== NoHookEffect) {
markComponentLayoutEffectMountStopped();
}
}
}
// 移动到下一个副作用
effect = effect.next;
} while (effect !== firstEffect); // 如果回到第一个副作用,结束循环
}
}
更新
由于useState第二个参数绑定着fiber concurrentQueues存着
找到根fiber,再次scheduleUpdateOnFiber
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
// 获取当前事件触发的时间戳,记录事件发生的时间
const eventTime = requestEventTime();
// 调度更新,处理当前 Fiber 节点上的更新任务
// root:当前 Fiber 树的根节点
// fiber:需要更新的 Fiber 节点
// lane:表示此次更新的优先级
// eventTime:事件触发的时间戳
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
// 处理过渡更新,使当前更新与其他更新建立联系
// root:当前 Fiber 树的根节点
// queue:当前 Fiber 节点上的 Hook 队列
// lane:表示此次更新的优先级
entangleTransitionUpdate(root, queue, lane);
}
export function addFiberToLanesMap(
root: FiberRoot, // Fiber 树的根节点
fiber: Fiber, // 当前需要更新的 Fiber 节点
lanes: Lanes | Lane, // 本次更新的 lane(更新优先级)
) {
// 如果没有启用 Updater 跟踪,则直接返回
if (!enableUpdaterTracking) {
return;
}
// 如果 DevTools 不存在,则不需要进行任何操作
if (!isDevToolsPresent) {
return;
}
// 从根节点获取 pendingUpdatersLaneMap,记录待处理的更新
const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;
// 循环处理 lanes,直到所有位都处理完
while (lanes > 0) {
// 将 lanes 转换为索引,用于找到对应的 lane
const index = laneToIndex(lanes);
// 使用位运算提取当前 lane
const lane = 1 << index;
// 获取对应 lane 的更新器集合
const updaters = pendingUpdatersLaneMap[index];
// 将当前 Fiber 节点添加到该 lane 对应的更新器集合中
updaters.add(fiber);
// 移除已处理的 lane,继续处理下一个
lanes &= ~lane;
}
}
接着ensureRootIsScheduled,prepareFreshStack(root, lanes);
createWorkInProgress
创建一个新的tree
// 如果已经有一个备用 Fiber 节点,则复用它
workInProgress.pendingProps = pendingProps;
// 更新 type,Blocks 类型组件会存储数据在 type 上
workInProgress.type = current.type;
// 重置 effect 标记,因为已经有 alternate 节点
workInProgress.flags = NoFlags;
// 清除无效的子树效果
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
// 如果启用了 Profiler Timer,则重置时间相关的属性
if (enableProfilerTimer) {
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = -1;
}
workLoopSync
又执行performUnitOfWork的beginWork
reconcileSingleElement
performSyncWorkOnRoot
root.finishedWork就是渲染阶段的workInProgress
commitRootImpl
发送消息,下一个事件循环执行dom操作
commitUpdate
调用setTextContent设置nodeValue,此时页面更新
commitPassiveUnmountEffects
// 执行副作用的销毁函数
function safelyCallDestroy(
current: Fiber,
nearestMountedAncestor: Fiber | null,
destroy: () => void,
) {
try {
destroy();
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}
}