Fiber结构
js
export type Fiber = {
// 一个"实例"在所有组件版本之间共享。我们可以轻松地将其拆分为一个独立对象,以避免向树的备用版本复制过多内容。
// 目前我们将其放在单个对象上,以最小化初始渲染期间创建的对象数量。
// 标识 fiber 类型的标签。
tag: WorkTag,
// 此子节点的唯一标识符。
key: null | string,
// element.type 的值,用于在协调此子节点时保持其身份。
elementType: any,
// 与此 fiber 关联的已解析的函数/类。
type: any,
// 与此 fiber 关联的本地状态(例如,对于DOM元素是DOM节点,对于类组件是实例)。
stateNode: any,
// 概念上的别名
// parent(父): Instance -> return(返回) 由于我们合并了fiber和实例,所以父节点恰好与返回fiber相同。
// 剩余的字段属于 Fiber
// 处理完此 fiber 后要返回的 Fiber。
// 这实际上是父级,但可能存在多个父级(两个),因此这仅是当前正在处理内容的父级。
// 它在概念上等同于堆栈帧的返回地址。
return: Fiber | null,
// 单链表树结构。
child: Fiber | null, // 第一个子 fiber
sibling: Fiber | null, // 下一个兄弟 fiber
index: number, // 在父fiber子节点列表中的索引
// 上次用于附加此节点的 ref。
// 为了生产环境,我将避免添加owner字段,并将其建模为函数。
ref:
| null
| (((handle: mixed) => void) & {_stringRef: ?string, ...})
| RefObject,
refCleanup: null | (() => void), // 用于清理 ref 的函数
// 输入是处理此 fiber 时传入的数据。参数。Props。
pendingProps: any, // 本次渲染待处理的 props
memoizedProps: any, // 上次渲染用于创建输出的 props
// 状态更新和回调函数的队列。
updateQueue: mixed,
// 用于创建输出的状态
memoizedState: any,
// 此 fiber 的依赖项(上下文、事件等),如果有的话
dependencies: Dependencies | null,
// 描述此 fiber 及其子树属性的位字段。
// 例如,ConcurrentMode 标志指示子树是否应默认为异步模式。
// 创建 fiber 时,它会继承其父级的模式。
// 创建时可以设置其他标志,但在此之后,该值应在 fiber 的整个生命周期内保持不变,尤其是在创建其子 fiber 之前。
mode: TypeOfMode,
// 副作用(Effect)
flags: Flags, // 记录此 fiber 上需要执行的副作用类型(如 Placement、Update、Deletion)
subtreeFlags: Flags, // 此 fiber 的子树中存在的副作用标志的集合
deletions: Array<Fiber> | null, // 记录此 fiber 子树中需要被删除的子 fiber 数组
// 优先级相关
lanes: Lanes, // 此 fiber 所属的调度车道(优先级)
childLanes: Lanes, // 子 fiber 的调度车道
// 这是一个 Fiber 的复用版本。每个被更新的 fiber 最终都会有一个对应的配对节点。
// 在某些情况下,如果需要节省内存,我们可以清理这些配对节点。
alternate: Fiber | null, // 指向 current 树和 workInProgress 树中对应节点的指针,用于双缓存技术。
// 为当前更新渲染此 Fiber 及其子节点所花费的时间。
// 这告诉我们这棵树利用 sCU 进行记忆化的效果如何。
// 每次渲染时它都会重置为 0,并且仅在我们没有"保释"时更新。
// 仅当 enableProfilerTimer 标志启用时才会设置此字段。
actualDuration?: number,
// 如果此 Fiber 当前正处于"渲染"阶段,
// 这会标记工作开始的时间。
// 仅当 enableProfilerTimer 标志启用时才会设置此字段。
actualStartTime?: number,
// 此 Fiber 最近一次渲染的持续时间。
// 当我们因记忆化目的而"保释"时,不会更新此值。
// 仅当 enableProfilerTimer 标志启用时才会设置此字段。
selfBaseDuration?: number,
// 此 Fiber 所有子代的基本时间总和。
// 此值在"完成"阶段向上冒泡。
// 仅当 enableProfilerTimer 标志启用时才会设置此字段。
treeBaseDuration?: number,
// 概念上的别名
// workInProgress(工作中) : Fiber -> alternate(备用) 用于复用的备用节点恰好与工作中的相同。
// 仅用于 __DEV__ 开发模式
_debugInfo?: ReactDebugInfo | null,
_debugOwner?: ReactComponentInfo | Fiber | null,
_debugStack?: string | Error | null,
_debugTask?: ConsoleTask | null,
_debugNeedsRemount?: boolean,
// 用于验证 Hook 的顺序在渲染之间是否发生改变。
_debugHookTypes?: Array<HookType> | null,
};
1.createRoot
一句话概括:创建root,并标记为根节点,返回root
js
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App title="合一" />
</StrictMode>
);
createRoot方法内部通过调用createContainer方法创建了一个root对象,又通过markContainerAsRoot方法将root标记为根节点。
js
export function createRoot(){
//1.创建了一个root对象
const root = createContainer(
container,// document.getElementById("root")!
...
);
// 2.标记为根节点
markContainerAsRoot(root.current, container);
//3.
return new ReactDOMRoot(root);
}
2.createContainer
createContainer方法返回一个createFiberRoot()创建的对象
js
export function createContainer(){
return createFiberRoot(
containerInfo,
tag,
hydrate,
...
);
}
3.createFiberRoot
1.返回一个通过new FiberRoot创建的对象,2.通过initializeUpdateQueue初始化更新队列
- 通过
new FiberRootNode创建一个root对象 - 通过
createHostRootFiber创建一个当前更新的Fiber树,可以有多个 initialState挂载到未初始化的Fiber上,记录当前的状态- 通过
initializeUpdateQueue初始化更新队列
js
export function createFiberRoot(){
//1.通过`new FiberRootNode`创建一个root对象
const root = new FiberRootNode(
containerInfo,
tag,
hydrate,
...
);
// 2.通过`createHostRootFiber`创建一个当前更新的那个Fiber
const uninitializedFiber = createHostRootFiber(tag, isStrictMode);
// 3.
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: initialCache,
};
//`initialState`挂载到未初始化的Fiber上,记录当前的状态
uninitializedFiber.memoizedState = initialState;
}
//4 .初始化更新队列
initializeUpdateQueue(uninitializedFiber);
//5.
return root;
createHostRootFiber
return createFiber() ,创建并返回一个 HostRoot 类型的 Fiber 节点。
js
export function createHostRootFiber(
tag: RootTag,
isStrictMode: boolean,
): Fiber {
let mode;
if (disableLegacyMode || tag === ConcurrentRoot) {
mode = ConcurrentMode; //启用并发特性(如时间切片、可中断渲染、过渡),提高交互与调度的弹性。
if (isStrictMode === true) {
mode |= StrictLegacyMode | StrictEffectsMode;
}
} else {
mode = NoMode; // Legacy 行为,不启用并发与严格增强。
}
if (enableProfilerTimer && isDevToolsPresent) {
// Always collect profile timings when DevTools are present.
// This enables DevTools to start capturing timing at any point--
// Without some nodes in the tree having empty base times.
mode |= ProfileMode;
}
return createFiber(HostRoot, null, null, mode);
}
mode类型
ConcurrentMode:启用并发特性(如时间切片、可中断渲染、过渡),提高交互与调度的弹性。NoMode:Legacy 行为,不启用并发与严格增强。StrictLegacyMode:开发环境下的严格模式旧行为,帮助暴露不安全的副作用与生命周期用法(如重复调用某些生命周期/副作用以检验幂等性)。StrictEffectsMode:严格执行副作用检查与行为一致性(开发环境下可能导致 useEffect 等二次调用,以检测副作用是否安全)。ProfileMode:记录渲染耗时、基线时间等性能数据,使 DevTools 能随时开始采样而不会出现空的基线时间。
createHostRootFiber 给你"树的根节点(Fiber)",负责承载应用元素状态与参与遍历。 createFiberRoot 给你"调度与容器(FiberRoot)",负责连接宿主环境、管理优先级与渲染生命周期。
- 两者通过 root.current 和 hostRootFiber.stateNode 形成双向关联:FiberRoot 管"何时渲染",HostRoot Fiber 管"渲染什么"。
initializeUpdateQueue
fiber.updateQueue = queue,初始化根节点的updateQueue
js
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
lanes: NoLanes,
hiddenCallbacks: null,
},
callbacks: null,
};
//关键
fiber.updateQueue = queue;
}
render
js
js
createRoot(document.getElementById("root")!).render(<App/>)
createRoot返回一个对象,render方法是挂载在这个对象的原型上的,内部主要调用了updateContainer
js
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render =
function(){
updateContainer(children, root, null, null);
}
updateContainer
- 通过
requestUpdateLane创建lan模型并返回 - 调用
updateContainerImpl方法
js
export function updateContainer(){
//1.通过`requestUpdateLane`创建lan模型
const lane = requestUpdateLane(current);
//2.
updateContainerImpl(
current,
lane,
...
);
//3.
return lane;
}
requestUpdateLane(current)
React 的 Lane 模型通过一个 31 位的二进制数来定义优先级,数值越小(二进制中低位为 1)的 Lane 优先级越高。下面这个表格能帮你快速了解所有预定义的 Lane 及其用途。
| 优先级类别 | 代表性 Lane 常量 | 二进制值 (示例) | 适用场景 |
|---|---|---|---|
| 最高优先级 (同步) | SyncLane |
0b0000000000000000000000000000001 |
必须同步执行的紧急更新,如用户输入、点击等直接影响交互的反馈。 |
| 高优先级 (用户阻塞) | InputContinuousLane |
0b0000000000000000000000000000100 |
连续的、不应阻塞但需及时响应的用户交互,如滚动、拖动。 |
| 默认优先级 | DefaultLane |
0b0000000000000000000000000010000 |
最常见的普通状态更新,如网络请求返回后更新数据。 |
| 过渡优先级 | TransitionLanes(共16条) |
0b0000000000000000000000001000000(示例) |
非紧急的界面过渡更新,如页面视图切换,可使用 startTransition或 useTransition标记。 |
| 低优先级 | RetryLanes, OffscreenLane |
例如 0b0000000010000000000000000000000 |
重试任务或离屏(尚未显示)内容的预渲染。 |
| 空闲优先级 | IdleLane |
0b0100000000000000000000000000000 |
完全非紧急的任务,仅在浏览器空闲时执行,如后台数据同步。 |
💡 理解 Lane 的运作方式
- 位的含义 :你可以将这 31 位想象成 31 条并行赛道。一位上的
1表示一个任务占用了该条车道。一个任务可以占用一条车道(一个单一的Lane),也可以同时占用多条车道(一个Lanes集合)。 - 优先级比较 :由于数值越小优先级越高 ,
SyncLane(二进制最低位为1)拥有最高的优先级。React 内部通过高效的位运算 (如按位与&、按位或|)来比较、合并或筛选不同优先级的任务,这比传统的数值比较要快得多 。 - 抢占式调度 :高优先级的任务可以中断(抢占)正在执行的低优先级任务。例如,当用户点击按钮(高优先级)时,正在进行的页面过渡渲染(低优先级)会被中断,以确保界面能立即响应用户操作 。
updateContainerImpl
- 通过
createUpdate创建update更新对象 - 通过
enqueueUpdate将update对象,追加到目标 Fiber 的更新队列中,通过并发入队把 lane 标到 fiber.lanes 以及 alternate.lanes。
ini
this.lanes = NoLanes; // 与React的并发模式有关的调度概念。
this.childLanes = NoLanes; // 与React的并发模式有关的调度概念。
this.alternate = NoLanes; // Current Tree和Work-in-progress (WIP) Tree的互相指向对方tree里的对应单元
- 调和调度更新队列
scheduleUpdateOnFiber
js
function updateContainerImpl(){
//1.
const update = createUpdate(lane);
//2.
const root = enqueueUpdate(rootFiber, update, lane);//通过并发入队把 lane 标到目标 Fiber( fiber.lanes 以及 alternate.lanes )
//3.
if (root !== null) {
scheduleUpdateOnFiber(root, rootFiber, lane);
}
}
scheduleUpdateOnFiber
scheduleUpdateOnFiber 函数,其核心职责就是扮演一个"通知者"和"标记员"的角色,确保更新的优先级( lane )被正确地记录在全局的"任务板"( root.pendingLanes )上,以便调度器能够看到并处理它。 这个过程非常像 医院的急诊分诊系统 :
- 病人到达 (产生更新) : 你调用 setState ,就像一个病人到达了医院急诊室。
- 分诊台护士评估 (获取 Lane) : 分诊台的护士( requestUpdateLane )会根据你的病情(比如是心梗还是普通感冒)给你一个紧急程度的标签,比如"危重"、"紧急"、"普通"。这个标签就是 lane 。
- 进入等候区 (更新入队) : 你拿着标签被引导到相应的等候区( enqueueUpdate ),和同样病情的其他病人一起等待。
- 通知总调度 (调用 scheduleUpdateOnFiber ) : 分诊护士会通知当班的护士长(总调度 Scheduler ):"来新病人了,已经分好级了!"
- 在中央白板上登记 (在 Root 上标记) : 护士长( scheduleUpdateOnFiber )走到医院大厅的中央白板( FiberRoot )前,在"待处理"一栏( pendingLanes )里,把这个病人的紧急级别( lane )登记上去。 这是关键一步 ,现在整个医院都知道有这个级别的病人需要处理。
- 医生接诊 (开始渲染) : 护士长看着白板上的所有登记,决定优先处理哪个级别的病人(比如永远先处理"危重"的),然后派出医生(开始渲染工作)去接诊。 所以, scheduleUpdateOnFiber 就好比是那个确保病人信息被准确登记到中央调度系统的关键角色,没有它,医生就不知道该去处理哪个病人了。
