Skip to content

第 5.1 节:ReactElement vs. Fiber:从蓝图到实体

在 React 的世界里,我们编写的 JSX 最终会经过一系列转换,才能成为屏幕上可见的 UI。这个过程的核心,离不开两个至关重要的概念:ReactElementFiber。理解它们的区别与联系,是深入 React 内部机制的钥匙。

可以把它们比作建筑过程中的“设计蓝图”和“施工脚手架”。

  • ReactElementUI 的设计蓝图。它是一个轻量、不可变的 JavaScript 对象,精确地描述了我们希望在屏幕上看到什么——组件的类型、属性(props)和子元素。
  • Fiber施工的脚手架和进度表。它是一个更复杂的数据结构,不仅包含了 ReactElement 的信息,还承载了组件的状态、副作用、在组件树中的层级关系以及更新的优先级。

1. ReactElement:不可变的 UI 描述

每当 React 渲染一个组件时,render 函数(或函数组件本身)的返回值就是一个或多个 ReactElement 对象。无论是通过 JSX 编译,还是直接调用 React.createElement,最终产物都是这个纯粹的描述性对象。

1.1. ReactElement 的结构

让我们回顾一下 ReactElement 的核心结构,它定义在 packages/react/src/ReactElement.js 中。

javascript
// 一个简化的 ReactElement 对象
const element = {
  $$typeof: Symbol.for('react.element'), // 标记其为 React 元素,防 XSS

  type: 'div', // 组件类型,可以是字符串(DOM 标签)或函数/类(组件)
  key: null,   // 用于列表 diff 优化的唯一标识
  props: {     // 属性,包括子元素
    className: 'container',
    children: 'Hello, World!'
  },

  // 以下为内部属性,主要用于开发和调试
  _owner: null, // 创建此元素的 Fiber 节点
  _store: {},
  // ...
};

核心特性

  • 轻量(Lightweight):它就是一个普通的 JS 对象,创建和销毁的成本极低。
  • 不可变(Immutable):一旦创建,ReactElement 的属性就不会被更改。任何 UI 的变动都会导致创建新的 ReactElement 树。
  • 描述性(Descriptive):它只负责“描述”UI 的样子,不包含任何状态管理或副作用处理的逻辑。

设计哲学:React 将“描述”和“实现”分离。ReactElement 作为纯粹的描述,使得 React 可以轻松地在不同环境(如浏览器、服务器、移动端)中使用相同的 UI 定义。同时,其不可变性也为后续的性能优化(如 memo)和状态追踪提供了基础。

2. Fiber:可变的渲染工作单元

如果说 ReactElement 是静态的蓝图,那么 Fiber 就是动态的施工单元。在协调(Reconciliation)阶段,React 会遍历 ReactElement 树,并为每个元素创建或更新一个对应的 Fiber 节点。这些 Fiber 节点连接起来,构成了一棵 Fiber 树,这棵树才是 React 进行增量渲染、优先级调度和状态管理的核心依据。

2.1. Fiber 节点的结构

Fiber 节点的结构远比 ReactElement 复杂,它定义在 packages/react-reconciler/src/ReactFiber.js 中。

javascript
// 一个简化的 FiberNode 构造函数
function FiberNode(tag, pendingProps, key, mode) {
  // --- 实例信息 ---
  this.tag = tag;             // WorkTag:标记 Fiber 类型,如 FunctionComponent
  this.key = key;             // key 属性
  this.elementType = null;    // ReactElement 的 type
  this.type = null;           // 同上,或 DOM 标签名
  this.stateNode = null;      // 指向真实的 DOM 节点或组件实例

  // --- 树状结构 ---
  this.return = null;         // 指向父 Fiber
  this.child = null;          // 指向第一个子 Fiber
  this.sibling = null;        // 指向下一个兄弟 Fiber

  // --- 工作单元状态 ---
  this.pendingProps = pendingProps; // 新的、待处理的 props
  this.memoizedProps = null;    // 上一次渲染使用的 props
  this.updateQueue = null;      // 状态更新和副作用的队列
  this.memoizedState = null;    // 上一次渲染的 state

  // --- 调度与副作用 ---
  this.flags = NoFlags;         // 副作用标记(如 Placement, Update, Deletion)
  this.lanes = NoLanes;         // 优先级标记
  this.childLanes = NoLanes;    // 子树的优先级

  // --- 双缓冲机制 ---
  this.alternate = null;      // 指向 work-in-progress 树或 current 树中的对应节点
}

核心特性

  • 工作单元(Unit of Work):一个 Fiber 节点就是一个独立的工作单元。React 可以处理一个或多个 Fiber 节点,然后暂停,将控制权交还给主线程。
  • 可变(Mutable):与 ReactElement 不同,Fiber 节点在协调过程中是可变的。React 会在 work-in-progress 树上复用和修改 Fiber 节点,以记录状态变化和需要执行的副作用。
  • 链表结构(Linked List):通过 returnchildsibling 指针,Fiber 节点构成了一个链表,使得遍历和中断操作变得非常高效。

3. 从 ReactElementFiber:转换的过程

这个转换发生在 beginWork 阶段的核心函数 reconcileChildren 中。当 React 处理一个父 Fiber 节点时,它会拿到父组件 render 函数返回的 ReactElement(或数组)。然后,它会遍历这些 ReactElement,并与父 Fibercurrent 树中的旧子 Fiber 节点进行 diff 比较。

  • 对于新的 ReactElement:会创建一个新的 Fiber 节点 (createFiberFromElement),并标记 Placement 副作用。
  • 对于 keytype 相同的 ReactElement:会复用旧的 Fiber 节点,并根据 props 的变化标记 Update 副作用。
  • 对于已不存在的旧 Fiber:会标记 Deletion 副作用。

这个过程就是 React 协调算法的核心,Fiber 节点作为 diff 的载体,精确地记录了从旧树到新树需要执行的所有变更。

4. 总结:职责的分离

特性ReactElement (设计蓝图)Fiber (施工脚手架)
本质纯粹的 JavaScript 对象复杂的数据结构,一个工作单元
可变性不可变 (Immutable)可变 (Mutable)
生命周期每次渲染都会重新创建在多次渲染之间被复用和更新
职责描述 UI 的静态结构和属性管理状态、副作用、优先级和渲染进度
数据结构简单的树状结构(通过 props.children包含父、子、兄弟指针的链表结构
存在于render 函数返回,存在于内存中存在于 current 树和 work-in-progress 树中

ReactElementFiber 的分离设计是 React 高性能和灵活性的关键。ReactElement 提供了一个简单、一致的 API 来描述 UI,而 Fiber 则在幕后处理了所有复杂的、与平台相关的调度和渲染工作。这种清晰的职责划分,使得 React 的核心逻辑既强大又易于维护。

Last updated: