React源码解读

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初始化更新队列

  1. 通过new FiberRootNode创建一个root对象
  2. 通过createHostRootFiber创建一个当前更新的Fiber树,可以有多个
  3. initialState挂载到未初始化的Fiber上,记录当前的状态
  4. 通过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

  1. 通过requestUpdateLane创建lan模型并返回
  2. 调用 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(示例) 非紧急的界面过渡更新,如页面视图切换,可使用 startTransitionuseTransition标记。
低优先级 RetryLanes, OffscreenLane 例如 0b0000000010000000000000000000000 重试任务或离屏(尚未显示)内容的预渲染。
空闲优先级 IdleLane 0b0100000000000000000000000000000 完全非紧急的任务,仅在浏览器空闲时执行,如后台数据同步。

💡 理解 Lane 的运作方式

  • 位的含义 :你可以将这 31 位想象成 31 条并行赛道。一位上的 1表示一个任务占用了该条车道。一个任务可以占用一条车道(一个单一的 Lane),也可以同时占用多条车道(一个 Lanes集合)。
  • 优先级比较 :由于数值越小优先级越高SyncLane(二进制最低位为1)拥有最高的优先级。React 内部通过高效的位运算 (如按位与 &、按位或 |)来比较、合并或筛选不同优先级的任务,这比传统的数值比较要快得多 。
  • 抢占式调度 :高优先级的任务可以中断(抢占)正在执行的低优先级任务。例如,当用户点击按钮(高优先级)时,正在进行的页面过渡渲染(低优先级)会被中断,以确保界面能立即响应用户操作 。

updateContainerImpl

  1. 通过createUpdate创建update更新对象
  2. 通过enqueueUpdateupdate对象,追加到目标 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里的对应单元
  1. 调和调度更新队列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 )上,以便调度器能够看到并处理它。 这个过程非常像 医院的急诊分诊系统 :

  1. 病人到达 (产生更新) : 你调用 setState ,就像一个病人到达了医院急诊室。
  2. 分诊台护士评估 (获取 Lane) : 分诊台的护士( requestUpdateLane )会根据你的病情(比如是心梗还是普通感冒)给你一个紧急程度的标签,比如"危重"、"紧急"、"普通"。这个标签就是 lane 。
  3. 进入等候区 (更新入队) : 你拿着标签被引导到相应的等候区( enqueueUpdate ),和同样病情的其他病人一起等待。
  4. 通知总调度 (调用 scheduleUpdateOnFiber ) : 分诊护士会通知当班的护士长(总调度 Scheduler ):"来新病人了,已经分好级了!"
  5. 在中央白板上登记 (在 Root 上标记) : 护士长( scheduleUpdateOnFiber )走到医院大厅的中央白板( FiberRoot )前,在"待处理"一栏( pendingLanes )里,把这个病人的紧急级别( lane )登记上去。 这是关键一步 ,现在整个医院都知道有这个级别的病人需要处理。
  6. 医生接诊 (开始渲染) : 护士长看着白板上的所有登记,决定优先处理哪个级别的病人(比如永远先处理"危重"的),然后派出医生(开始渲染工作)去接诊。 所以, scheduleUpdateOnFiber 就好比是那个确保病人信息被准确登记到中央调度系统的关键角色,没有它,医生就不知道该去处理哪个病人了。
相关推荐
Mintopia2 小时前
🌱 AIGC 技术的轻量化趋势:Web 端“小而美”模型的崛起
前端·javascript·aigc
开发者小天2 小时前
React中的useRef的用法
开发语言·前端·javascript·react.js
im_AMBER2 小时前
React 11 登录页项目框架搭建
前端·学习·react.js·前端框架
Live&&learn2 小时前
nvm切换node版本时,npm不跟着切换解决
前端·npm·node.js
xixixin_2 小时前
【React】检测元素是否出现在用户视窗内
开发语言·前端·javascript·react.js
谢彦超oooo3 小时前
HTML5 与前端开发要点
前端·html·html5
IT_陈寒4 小时前
Vue 3响应式原理深度拆解:5个90%开发者不知道的Ref与Reactive底层实现差异
前端·人工智能·后端
睡前要喝豆奶粉4 小时前
在.NET Core Web Api中使用JWT并配置UserContext获取用户信息
前端·.netcore
前端加油站4 小时前
一份实用的Vue3技术栈代码评审指南
前端·vue.js