Fiber 结构和普通 VNode 区别

Fiber 结构和普通 VNode 区别

Date: February 10, 2025

Area: 原理

Question: Fiber 结构和普通 VNode 区别

Review: 25/11/27

本质差异

普通 VNode

UI结构的静态快照 ,一个轻量的 JavaScript 对象,描述了真实 DOM 应该是什么样子(标签名、属性、子节点等)。它的核心目的是通过 Diff 算法对比新旧 VNode 树,计算出最小变更,最后统一更新真实 DOM,以减少性能开销。

Fiber Node

React 内部的工作单元 。Fiber 架构将复杂的渲染更新过程拆分成多个小任务单元 (每个 Fiber 节点就是一个单元),并赋予它们不同的优先级。这使得 React 可以在浏览器空闲时执行这些任务,高优先级的更新(如用户输入)可以中断低优先级的更新(如渲染大数据列表),从而实现增量渲染,极大提升应用的响应能力

特性维度 普通 VNode (Virtual DOM Node) React Fiber Node
本质目的 UI 的静态描述,描述"是什么" 动态的工作单元,描述"如何做"
数据结构 通常为嵌套的树形结构 (通过 children属性连接) 链表树 (通过 child, sibling, return指针连接)
更新过程 同步递归,不可中断,一旦开始直到整棵树 Diff 完成 可中断的异步增量更新,将任务拆分成小单元,可暂停、继续和优先级调度
数据存储 主要存储与渲染相关的信息 (类型、属性、子节点) 存储大量状态、副作用、优先级等信息,是承载组件状态和更新逻辑的实体
与实例关系 与组件实例或 DOM 元素无强关联 通过 stateNode指向类组件实例、DOM 节点或其他 React 元素类型
生命周期 短暂,每次渲染都会重新创建,旧树可被垃圾回收 持久化 ,在多次渲染间复用,通过 alternate指针连接当前树和 workInProgress 树
核心优化 通过 Diff 算法减少对真实 DOM 的直接操作 时间切片 (Time Slicing)优先级调度,避免阻塞主线程

数据结构对比

普通 VNode(React 15 及之前)

通常是树形结构 。父节点通过 children属性引用子节点数组,形成一个嵌套的树。这种结构遍历时通常需要递归,一旦开始难以中断。

jsx 复制代码
const vNode = {
  type: 'div', // 节点类型(组件/原生标签)
  props: { className: 'container' }, // 属性
  children: [vNode1, vNode2], // 子节点(树形结构)
  key: 'unique-id', // 优化 Diff 性能
  // 无状态、调度、副作用信息
}
  • 核心字段:仅包含 UI 描述相关属性(type、props、children)。

Fiber Node(React 16+)

链表树结构 (单链表)。每个 Fiber 节点包含 child(第一个子节点)、sibling(下一个兄弟节点)和 return(父节点)指针。这种结构使得遍历可以不需要递归 ,只需循环即可,从而实现了可中断的深度优先遍历

Case:

jsx 复制代码
function App() {
	return (
		<div>
			Hello
			<span>World</span>
		</div>
	)
}

Fiber Node 数据结构:

jsx 复制代码
export type Fiber = {
  // --- 组件类型与标识 ---
  tag: WorkTag, // 标记fiber的类型 (函数组件、类组件、宿主组件、Fragment等)[1,2](@ref)
  key: null | string, // 唯一标识,用于协调过程中识别同级节点[1](@ref)
  type: any, // 与fiber关联的函数/类/类型 (对于函数组件是函数本身,对于原生标签是字符串)[1](@ref)
  elementType: any, // 元素类型,用于在协调期间保留身份标识
  stateNode: any, // 对应的真实实例 (DOM节点、类组件实例或FiberRoot)[1,2](@ref)

  // --- Fiber树结构指针 (链表结构) ---
  return: Fiber | null, // 指向父级Fiber节点[2,3](@ref)
  child: Fiber | null, // 指向第一个子Fiber节点[2,3](@ref)
  sibling: Fiber | null, // 指向下一个兄弟Fiber节点[2,3](@ref)
  index: number, // 在同级节点中的索引位置,用于Diff算法

  // --- 数据与状态 ---
  pendingProps: any, // 新的、尚未处理的props[1](@ref)
  memoizedProps: any, // 上一次渲染时使用的props[1](@ref)
  memoizedState: any, // 上一次渲染时使用的状态 (函数组件的hooks链表、类组件的state、HostRoot的state)[4,5](@ref)
  updateQueue: mixed, // 存储状态更新和回调函数的队列[1](@ref)

  // --- 副作用与更新标记 ---
  flags: Flags, // 标记当前Fiber需要执行的副作用类型 (如Placement, Update, Deletion)[3,5](@ref)
  subtreeFlags: Flags, // 标记子树中需要执行的副作用
  deletions: Array<Fiber> | null, // 记录需要被删除的子Fiber节点
  lanes: Lanes, // 优先级车道 (React 17+),表示任务的紧迫程度[1,2](@ref)
  childLanes: Lanes, // 子树中的优先级车道

  // --- 双缓存机制 ---
  alternate: Fiber | null, // 指向当前树或workInProgress树中的对应节点,用于复用和比较[3,5](@ref)
};

核心扩展

  • 调度控制lanes 优先级、任务到期时间。
  • 状态管理:Hooks 链表(函数组件)、类组件状态队列。
  • 副作用追踪effectTag 标记和副作用链表。
  • 遍历结构child/sibling/return 构成双向链表。

协调机制对比

流程 VNode(Stack Reconciler) Fiber Reconciler
遍历方式 递归遍历(不可中断) 循环遍历链表(可中断 + 恢复)
任务调度 同步执行,阻塞主线程 异步分片,空闲时间执行
优先级控制 Lane 模型(31 个优先级车道)
副作用处理 统一提交 DOM 更新 构建副作用链表,分阶段提交

Fiber 两阶段提交

  1. 协调阶段 (可中断):
    • 增量构建 Fiber 树,标记副作用(effectTag)。
    • 通过 requestIdleCallback 或 Scheduler 包分片执行。
  2. 提交阶段 (同步不可中断):
    • 遍历副作用链表,执行 DOM 操作和生命周期方法。

双缓存技术

这是 Fiber 架构的一个精妙之处。React 同时维护两棵 Fiber 树:

Current Tree:"真实 UI 对应的 Fiber Tree",代表当前屏幕上显示的状态。

WorkInProgress Tree:"正在内存中构建的 Fiber Tree",正在内存中构建的新状态。

两棵树通过 alternate指针相互连接。当 workInProgress树构建完成,在 Commit 阶段渲染后,React 会将 current指针指向它,于是 workInProgress树就变成了新的 current树。这种交换技术能确保更新的连贯性,避免中间状态对用户可见。


能力扩展示例

a. 支持 Hooks 状态管理

  • Fiber 节点通过 memoizedState 字段存储 Hooks 链表:
jsx 复制代码
// 函数组件的 Hooks 链表
fiberNode.memoizedState = {
  memoizedState: 'state value', // useState 的状态
  next: {
    // 下一个 Hook(如 useEffect)
    memoizedState: { cleanup: fn },
    next: null,
  },
}
  • VNode 无状态管理能力,仅描述 UI。

b. 优先级调度实战

  • 高优先级任务抢占

    jsx 复制代码
    // 用户输入触发高优先级更新
    input.addEventListener('input', () => {
      React.startTransition(() => {
        setInputValue(e.target.value) // 低优先级
      })
      // 高优先级更新立即执行
    })
  • VNode 架构无法实现任务中断和优先级插队。

c. 副作用批处理

  • Fiber 通过 effectList 链表收集所有变更,统一提交:

    jsx 复制代码
    // 提交阶段遍历 effectList
    let nextEffect = fiberRoot.firstEffect
    while (nextEffect) {
      commitWork(nextEffect)
      nextEffect = nextEffect.nextEffect
    }
  • VNode 架构在 Diff 后直接操作 DOM,无批处理优化。


性能影响对比

场景 VNode 架构 Fiber 架构
大型组件树渲染 主线程阻塞导致掉帧 分片渲染,保持 UI 响应
高频更新(如动画) 多次渲染合并困难 基于优先级合并或跳过中间状态
SSR 水合(Hydration) 全量同步处理 增量水合,优先交互部分
相关推荐
Bigger13 小时前
🚀 “踩坑日记”:shadcn + Vite 在 Monorepo 中配置报错
前端·react.js·vite
一只爱吃糖的小羊15 小时前
🕳️ React 避坑指南:"闭包陷阱"
react.js
wordbaby16 小时前
queries(查询)
前端·react.js
wordbaby16 小时前
important-defaults(重要的默认配置)
前端·react.js
余生H19 小时前
前端技术新闻(WTN-1):React.js & Next.js 爆出 CVSS 10.0 级严重漏洞,历史风险回顾与代码级深度分析
前端·javascript·react.js
古韵21 小时前
如何从Axios平滑迁移到Alova
vue.js·react.js·node.js
dorisrv1 天前
React 状态管理:Zustand 快速上手指南
前端·react.js
前端老宋Running1 天前
你的组件 API 为什么像个垃圾场?—— React 复合组件模式 (Compound Components) 实战教学
前端·react.js·架构
前端小臻1 天前
react中的函数组件和类组件(快捷指令和区别)
前端·react.js·前端框架