Skip to content

第4章 Fiber架构

本章将深入React的Fiber架构,理解Fiber节点的数据结构、WorkTag类型系统和双缓冲机制。这是理解React协调算法的基础。

在前面的章节中,我们了解了React的整体架构和JSX的编译过程。从本章开始,我们将深入React客户端渲染的核心实现。首先要理解的是Fiber架构——这是React 16引入的革命性设计,它使得React能够实现可中断的渲染、优先级调度和并发特性。

什么是Fiber?为什么React需要Fiber架构?Fiber节点包含哪些信息?不同类型的组件对应什么样的Fiber节点?双缓冲机制是如何工作的?

本章将逐一解答这些问题,为后续学习调度系统和协调算法打下坚实基础。


4.1 什么是Fiber

Fiber是React 16引入的核心概念,它彻底改变了React的内部实现。要理解Fiber,我们需要先了解React为什么需要它。

4.1.1 Fiber的双重含义

在React中,Fiber有两层含义:

1. 架构层面:Fiber是React的新协调引擎

React 16之前使用的是Stack Reconciler(栈协调器),它采用递归的方式遍历组件树。一旦开始渲染,就无法中断,必须一次性完成整个组件树的协调工作。

React 16引入了Fiber Reconciler(Fiber协调器),它将渲染工作分解成多个小任务,可以在浏览器空闲时执行,也可以根据优先级中断和恢复。

2. 数据结构层面:Fiber是一个JavaScript对象

每个React元素对应一个Fiber节点,Fiber节点保存了组件的类型、状态、props等信息,以及与其他Fiber节点的关系。

javascript
// Fiber节点的简化结构
const fiber = {
  // 节点类型信息
  tag: FunctionComponent,        // 组件类型标识
  type: MyComponent,             // 组件函数或类
  key: 'unique-key',            // 列表中的唯一标识
  
  // 节点关系
  return: parentFiber,          // 父节点
  child: firstChildFiber,       // 第一个子节点
  sibling: nextSiblingFiber,    // 下一个兄弟节点
  
  // 节点状态
  memoizedState: { count: 0 },  // 当前状态
  memoizedProps: { name: 'A' }, // 当前props
  pendingProps: { name: 'B' },  // 新的props
  
  // 副作用
  flags: Update | Placement,    // 需要执行的操作
  
  // 双缓冲
  alternate: workInProgressFiber, // 对应的另一棵树的节点
};

4.1.2 从Stack到Fiber的演进

为什么React要从Stack Reconciler切换到Fiber Reconciler?让我们通过一个例子来理解。

Stack Reconciler的问题

假设我们有一个包含1000个组件的大型应用:

jsx
function App() {
  return (
    <div>
      {Array.from({ length: 1000 }).map((_, i) => (
        <ExpensiveComponent key={i} />
      ))}
    </div>
  );
}

在Stack Reconciler中,当状态更新触发重新渲染时:

开始渲染 ──▶ 递归处理1000个组件 ──▶ 渲染完成
    │                │                  │
    ▼                ▼                  ▼
  0ms             50ms               100ms
    
浏览器主线程被占用,无法响应用户交互
用户点击按钮 ──▶ 等待... ──▶ 等待... ──▶ 终于响应

问题在于:

  1. 无法中断:递归调用栈一旦开始就无法停止
  2. 阻塞主线程:长时间占用主线程,导致页面卡顿
  3. 无法优先级调度:所有更新都是同等优先级

Fiber Reconciler的解决方案

Fiber将渲染工作分解成多个小任务:

开始渲染 ──▶ 处理100个组件 ──▶ 让出控制权 ──▶ 继续处理 ──▶ ...
    │              │                │              │
    ▼              ▼                ▼              ▼
  0ms           5ms              10ms           15ms
    
浏览器主线程可以处理其他任务
用户点击按钮 ──▶ 立即响应 ──▶ 暂停渲染 ──▶ 处理点击 ──▶ 继续渲染

Fiber的优势:

  1. 可中断:渲染工作可以被打断,稍后继续
  2. 时间切片:将工作分散到多个帧中执行
  3. 优先级调度:高优先级任务可以插队执行

4.1.3 可中断渲染的实现思路

Stack Reconciler使用递归,而递归是无法中断的。Fiber如何实现可中断渲染?

关键思想:将递归转换为循环

递归版本(Stack Reconciler):

javascript
function reconcileChildren(parent) {
  parent.children.forEach(child => {
    processComponent(child);
    reconcileChildren(child); // 递归调用
  });
}

循环版本(Fiber Reconciler):

javascript
function workLoop() {
  while (workInProgress !== null && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(fiber) {
  // 处理当前Fiber
  processComponent(fiber);
  
  // 返回下一个要处理的Fiber
  if (fiber.child) {
    return fiber.child;
  }
  if (fiber.sibling) {
    return fiber.sibling;
  }
  return fiber.return; // 回到父节点
}

通过循环,React可以在每次迭代后检查是否需要让出控制权:

javascript
function shouldYield() {
  // 检查是否超过时间片(默认5ms)
  return getCurrentTime() >= deadline;
}

Fiber树的遍历

Fiber节点通过childsiblingreturn三个指针形成树结构:

        A
       / \
      B   C
     /     \
    D       E

Fiber树的遍历顺序:
A → B → D → C → E

具体过程:
1. 从A开始,有child,进入B
2. 从B开始,有child,进入D
3. D没有child,没有sibling,return到B
4. B没有sibling,return到A
5. A有sibling C,进入C
6. C有child,进入E
7. E没有child,没有sibling,return到C
8. C没有sibling,return到A
9. A没有sibling,遍历结束

这种遍历方式使得React可以在任意节点暂停,并在稍后从该节点继续。


4.2 Fiber节点的核心属性

理解了Fiber的概念后,让我们深入Fiber节点的数据结构。Fiber节点包含大量属性,我们将它们分为四类:静态属性、关系属性、状态属性和副作用属性。

4.2.1 静态属性:tag、type、key

tag属性

tag是一个数字,标识Fiber节点的类型。它决定了React如何处理这个节点。

javascript
// 文件:packages/react-reconciler/src/ReactFiber.js
// 行号:140-180(React 19.3.0)

function FiberNode(
  this: $FlowFixMe,
  tag: WorkTag,
  pendingProps: mixed,
  key: ReactKey,
  mode: TypeOfMode,
) {
  // Instance
  this.tag = tag;              // 节点类型标识
  this.key = key;              // 列表中的唯一标识
  this.elementType = null;     // React元素类型
  this.type = null;            // 组件函数或类
  this.stateNode = null;       // 对应的DOM节点或组件实例
  
  // ... 其他属性
}

常见的tag值(定义在ReactWorkTags.js中):

javascript
// 文件:packages/react-reconciler/src/ReactWorkTags.js

export const FunctionComponent = 0;      // 函数组件
export const ClassComponent = 1;         // 类组件
export const HostRoot = 3;              // 根节点
export const HostComponent = 5;         // 原生DOM元素
export const HostText = 6;              // 文本节点
export const Fragment = 7;              // Fragment
export const SuspenseComponent = 13;    // Suspense组件
export const MemoComponent = 14;        // React.memo包装的组件

type属性

type保存了组件的实际类型:

javascript
// 函数组件
function MyComponent() { return <div />; }
// fiber.type = MyComponent

// 类组件
class MyClass extends React.Component {
  render() { return <div />; }
}
// fiber.type = MyClass

// 原生DOM元素
<div />
// fiber.type = 'div'

// Fragment
<><div /></>
// fiber.type = Symbol.for('react.fragment')

key属性

key用于列表渲染时的元素识别:

javascript
const list = ['A', 'B', 'C'].map(item => (
  <li key={item}>{item}</li>
));
// 每个li的Fiber节点都有对应的key

key的作用将在第6章"协调过程"中详细讲解。

4.2.2 关系属性:return、child、sibling

Fiber节点通过三个指针形成树结构:

javascript
// 文件:packages/react-reconciler/src/ReactFiber.js

function FiberNode(/*...*/) {
  // ...
  
  // Fiber树的关系指针
  this.return = null;    // 父节点
  this.child = null;     // 第一个子节点
  this.sibling = null;   // 下一个兄弟节点
  this.index = 0;        // 在兄弟节点中的索引
  
  // ...
}

return指针

指向父Fiber节点。为什么叫return而不是parent?因为在Fiber的工作循环中,处理完一个节点后会"返回"到父节点。

child指针

指向第一个子Fiber节点。注意:只保存第一个子节点,其他子节点通过sibling链接。

sibling指针

指向下一个兄弟Fiber节点。

示例:Fiber树结构

jsx
function App() {
  return (
    <div>
      <h1>Title</h1>
      <p>Content</p>
    </div>
  );
}

对应的Fiber树:

        App (FunctionComponent)

         │ child

        div (HostComponent)

         │ child

        h1 (HostComponent) ──sibling──▶ p (HostComponent)
         │                               │
         │ child                         │ child
         ▼                               ▼
      "Title" (HostText)            "Content" (HostText)

每个节点的关系指针:

javascript
// App Fiber
{
  tag: FunctionComponent,
  type: App,
  return: null,
  child: divFiber,
  sibling: null
}

// div Fiber
{
  tag: HostComponent,
  type: 'div',
  return: appFiber,
  child: h1Fiber,
  sibling: null
}

// h1 Fiber
{
  tag: HostComponent,
  type: 'h1',
  return: divFiber,
  child: titleTextFiber,
  sibling: pFiber
}

// p Fiber
{
  tag: HostComponent,
  type: 'p',
  return: divFiber,
  child: contentTextFiber,
  sibling: null
}

4.2.3 状态属性:memoizedState、memoizedProps

Fiber节点保存了组件的状态和props:

javascript
// 文件:packages/react-reconciler/src/ReactFiber.js

function FiberNode(/*...*/) {
  // ...
  
  this.pendingProps = pendingProps;  // 新的props(即将应用)
  this.memoizedProps = null;         // 当前的props(已应用)
  this.updateQueue = null;           // 更新队列
  this.memoizedState = null;         // 当前的state
  this.dependencies = null;          // 依赖的context等
  
  // ...
}

memoizedState

保存组件的状态。对于不同类型的组件,memoizedState的含义不同:

javascript
// 函数组件:保存Hooks链表
function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('React');
  // fiber.memoizedState = {
  //   memoizedState: 0,        // count的值
  //   next: {
  //     memoizedState: 'React', // name的值
  //     next: null
  //   }
  // }
}

// 类组件:保存state对象
class Counter extends React.Component {
  state = { count: 0 };
  // fiber.memoizedState = { count: 0 }
}

// HostRoot:保存待处理的更新
// fiber.memoizedState = {
//   element: <App />,  // 根元素
//   // ...
// }

memoizedProps

保存组件当前的props:

javascript
<Button color="blue" size="large">Click</Button>

// Button的Fiber节点
{
  memoizedProps: {
    color: 'blue',
    size: 'large',
    children: 'Click'
  }
}

pendingProps

保存新的props。在协调过程中,React会比较memoizedPropspendingProps来决定是否需要更新组件。

javascript
// 更新前
fiber.memoizedProps = { color: 'blue' }
fiber.pendingProps = { color: 'red' }

// 更新后
fiber.memoizedProps = { color: 'red' }

updateQueue

保存组件的更新队列。对于类组件,这是setState产生的更新;对于函数组件,这是useStatedispatch产生的更新。

javascript
// 类组件的updateQueue
class Counter extends React.Component {
  handleClick = () => {
    this.setState({ count: 1 });
    this.setState({ count: 2 });
    this.setState({ count: 3 });
  }
  
  // fiber.updateQueue = {
  //   baseState: { count: 0 },
  //   firstBaseUpdate: null,
  //   lastBaseUpdate: null,
  //   shared: {
  //     pending: {
  //       payload: { count: 3 },
  //       next: {
  //         payload: { count: 2 },
  //         next: {
  //           payload: { count: 1 },
  //           next: null
  //         }
  //       }
  //     }
  //   }
  // }
}

4.2.4 副作用属性:flags、subtreeFlags

Fiber节点使用flags标记需要执行的副作用(side effects):

javascript
// 文件:packages/react-reconciler/src/ReactFiber.js

function FiberNode(/*...*/) {
  // ...
  
  // Effects
  this.flags = NoFlags;          // 当前节点的副作用标记
  this.subtreeFlags = NoFlags;   // 子树的副作用标记
  this.deletions = null;         // 需要删除的子节点
  
  // ...
}

flags(副作用标记)

flags是一个位掩码,使用二进制位表示不同的副作用:

javascript
// 文件:packages/react-reconciler/src/ReactFiberFlags.js

export const NoFlags = 0b0000000000000000000000000000;
export const Placement = 0b0000000000000000000000000010;  // 插入
export const Update = 0b0000000000000000000000000100;     // 更新
export const Deletion = 0b0000000000000000000000001000;   // 删除
export const ChildDeletion = 0b0000000000000000000010000; // 子节点删除
export const Ref = 0b0000000000000000001000000000;        // ref更新
export const Passive = 0b0000000000000010000000000000;    // useEffect
export const LayoutMask = 0b0000000000000100000000000000; // useLayoutEffect

使用位运算操作flags:

javascript
// 添加flag
fiber.flags |= Placement;
fiber.flags |= Update;

// 检查flag
if (fiber.flags & Placement) {
  // 需要插入DOM
}

// 移除flag
fiber.flags &= ~Placement;

subtreeFlags(子树副作用标记)

subtreeFlags是子树中所有副作用的汇总。这是一个优化:如果subtreeFlags为空,说明子树没有任何副作用,可以跳过遍历。

javascript
// 示例:计算subtreeFlags
function completeWork(fiber) {
  let subtreeFlags = NoFlags;
  
  // 收集子节点的flags
  let child = fiber.child;
  while (child !== null) {
    subtreeFlags |= child.flags;
    subtreeFlags |= child.subtreeFlags;
    child = child.sibling;
  }
  
  fiber.subtreeFlags = subtreeFlags;
}

deletions(待删除节点)

保存需要删除的子Fiber节点:

javascript
// 更新前
<div>
  <span>A</span>
  <span>B</span>
  <span>C</span>
</div>

// 更新后
<div>
  <span>A</span>
  <span>C</span>
</div>

// div的Fiber节点
{
  flags: ChildDeletion,
  deletions: [spanBFiber]  // B需要被删除
}

4.3 WorkTag类型详解

Fiber节点的tag属性标识了节点的类型。React定义了30多种WorkTag,每种类型对应不同的处理逻辑。本节将详细介绍最常用的几种类型。

4.3.1 FunctionComponent与HostComponent

FunctionComponent(函数组件)

javascript
// 文件:packages/react-reconciler/src/ReactWorkTags.js
export const FunctionComponent = 0;

函数组件是最常见的组件类型:

jsx
function Welcome({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// 对应的Fiber节点
{
  tag: FunctionComponent,  // 0
  type: Welcome,           // 组件函数
  pendingProps: { name: 'React' },
  memoizedState: null,     // Hooks链表(如果使用了Hooks)
}

React处理FunctionComponent时,会调用组件函数获取返回的React元素:

javascript
// 简化版 - 文件:packages/react-reconciler/src/ReactFiberBeginWork.js

function updateFunctionComponent(current, workInProgress, Component, nextProps) {
  // 调用组件函数
  const nextChildren = Component(nextProps);
  
  // 协调子节点
  reconcileChildren(current, workInProgress, nextChildren);
  
  return workInProgress.child;
}

HostComponent(原生DOM元素)

javascript
// 文件:packages/react-reconciler/src/ReactWorkTags.js
export const HostComponent = 5;

HostComponent表示原生DOM元素:

jsx
<div className="container">
  <span>Text</span>
</div>

// div的Fiber节点
{
  tag: HostComponent,      // 5
  type: 'div',             // 元素类型
  pendingProps: {
    className: 'container',
    children: [/*...*/]
  },
  stateNode: divDOMNode,   // 对应的真实DOM节点
}

React处理HostComponent时,会创建或更新真实的DOM节点:

javascript
// 简化版 - 文件:packages/react-reconciler/src/ReactFiberCompleteWork.js

function completeWork(current, workInProgress) {
  const type = workInProgress.type;
  const props = workInProgress.pendingProps;
  
  if (current === null) {
    // 首次渲染:创建DOM节点
    const instance = document.createElement(type);
    workInProgress.stateNode = instance;
    
    // 设置属性
    setInitialProperties(instance, type, props);
  } else {
    // 更新:标记需要更新的属性
    const oldProps = current.memoizedProps;
    const updatePayload = diffProperties(instance, type, oldProps, props);
    workInProgress.updateQueue = updatePayload;
    
    if (updatePayload) {
      workInProgress.flags |= Update;
    }
  }
}

HostText(文本节点)

javascript
// 文件:packages/react-reconciler/src/ReactWorkTags.js
export const HostText = 6;

HostText表示文本节点:

jsx
<div>Hello World</div>

// "Hello World"的Fiber节点
{
  tag: HostText,           // 6
  type: null,
  pendingProps: 'Hello World',
  stateNode: textDOMNode,  // 对应的Text节点
}

4.3.2 SuspenseComponent

javascript
// 文件:packages/react-reconciler/src/ReactWorkTags.js
export const SuspenseComponent = 13;

Suspense组件用于处理异步加载:

jsx
<Suspense fallback={<Loading />}>
  <AsyncComponent />
</Suspense>

// Suspense的Fiber节点
{
  tag: SuspenseComponent,  // 13
  type: Symbol.for('react.suspense'),
  pendingProps: {
    fallback: <Loading />,
    children: <AsyncComponent />
  },
  memoizedState: {
    dehydrated: null,      // SSR相关
    retryLane: NoLane,     // 重试的优先级
  }
}

Suspense的工作原理:

  1. 捕获Promise:当子组件抛出Promise时,Suspense会捕获它
  2. 显示fallback:在Promise pending期间,显示fallback内容
  3. 重新渲染:Promise resolve后,重新渲染子组件
javascript
// 简化版 - Suspense的处理逻辑

function updateSuspenseComponent(current, workInProgress) {
  const nextProps = workInProgress.pendingProps;
  
  try {
    // 尝试渲染children
    const nextChildren = nextProps.children;
    reconcileChildren(current, workInProgress, nextChildren);
  } catch (promise) {
    if (isPromise(promise)) {
      // 捕获到Promise,显示fallback
      const nextFallback = nextProps.fallback;
      reconcileChildren(current, workInProgress, nextFallback);
      
      // 等待Promise resolve
      promise.then(() => {
        // 重新调度渲染
        scheduleUpdateOnFiber(workInProgress);
      });
    } else {
      throw promise;
    }
  }
}

4.3.3 OffscreenComponent

javascript
// 文件:packages/react-reconciler/src/ReactWorkTags.js
export const OffscreenComponent = 22;

OffscreenComponent用于实现内容的显示/隐藏,而不销毁组件状态:

jsx
// React内部使用,通常不直接暴露给开发者
<Offscreen mode={visible ? 'visible' : 'hidden'}>
  <ExpensiveComponent />
</Offscreen>

OffscreenComponent的特点:

  1. 保留状态:隐藏时不卸载组件,保留state和DOM
  2. 跳过渲染:隐藏时跳过子树的渲染和副作用
  3. 快速切换:显示时无需重新挂载,性能更好
javascript
// Offscreen的Fiber节点
{
  tag: OffscreenComponent,  // 22
  type: null,
  memoizedState: {
    mode: 'hidden',         // 'visible' | 'hidden'
    cache: null,            // 缓存的内容
  }
}

OffscreenComponent在以下场景中使用:

  • Suspense的fallback:fallback内容被包裹在Offscreen中
  • Activity组件:React 19新增的Activity组件基于Offscreen实现
  • 预渲染:提前渲染但不显示的内容

4.3.4 示例:Fiber树结构图解

让我们通过一个完整的例子来理解不同类型的Fiber节点如何组成Fiber树:

jsx
function App() {
  const [show, setShow] = useState(true);
  
  return (
    <div className="app">
      <h1>My App</h1>
      {show && <Content />}
      <Suspense fallback={<Loading />}>
        <AsyncData />
      </Suspense>
    </div>
  );
}

function Content() {
  return <p>Content here</p>;
}

function Loading() {
  return <div>Loading...</div>;
}

async function AsyncData() {
  const data = await fetchData();
  return <div>{data}</div>;
}

对应的Fiber树结构:

HostRoot (tag: 3)

  │ child

App (tag: 0, FunctionComponent)

  │ child

div.app (tag: 5, HostComponent)

  │ child

h1 (tag: 5, HostComponent) ──sibling──▶ Content (tag: 0) ──sibling──▶ Suspense (tag: 13)
  │                                       │                              │
  │ child                                 │ child                        │ child
  ▼                                       ▼                              ▼
"My App" (tag: 6, HostText)             p (tag: 5)                    Offscreen (tag: 22)
                                          │                              │
                                          │ child                        │ child
                                          ▼                              ▼
                                        "Content here" (tag: 6)        AsyncData (tag: 0)

                                                                          │ child

                                                                        div (tag: 5)

                                                                          │ child

                                                                        data (tag: 6)

每个节点的详细信息:

javascript
// HostRoot
{
  tag: 3,
  type: null,
  stateNode: FiberRootNode,
  child: appFiber
}

// App (FunctionComponent)
{
  tag: 0,
  type: App,
  memoizedState: {
    memoizedState: true,  // useState的值
    next: null
  },
  child: divFiber
}

// div.app (HostComponent)
{
  tag: 5,
  type: 'div',
  pendingProps: {
    className: 'app',
    children: [/*...*/]
  },
  stateNode: <div class="app">...</div>,
  child: h1Fiber
}

// h1 (HostComponent)
{
  tag: 5,
  type: 'h1',
  stateNode: <h1>...</h1>,
  child: textFiber,
  sibling: contentFiber
}

// "My App" (HostText)
{
  tag: 6,
  type: null,
  pendingProps: 'My App',
  stateNode: Text("My App")
}

// Content (FunctionComponent)
{
  tag: 0,
  type: Content,
  child: pFiber,
  sibling: suspenseFiber
}

// Suspense (SuspenseComponent)
{
  tag: 13,
  type: Symbol.for('react.suspense'),
  pendingProps: {
    fallback: <Loading />,
    children: <AsyncData />
  },
  child: offscreenFiber
}

// Offscreen (OffscreenComponent)
{
  tag: 22,
  type: null,
  memoizedState: {
    mode: 'visible'
  },
  child: asyncDataFiber
}

// AsyncData (FunctionComponent)
{
  tag: 0,
  type: AsyncData,
  child: divFiber
}

WorkTag类型总结

WorkTag说明示例
FunctionComponent0函数组件function App() {}
ClassComponent1类组件class App extends React.Component {}
HostRoot3根节点createRoot(container)
HostComponent5原生DOM元素<div>, <span>
HostText6文本节点"Hello"
Fragment7Fragment<></>
SuspenseComponent13Suspense组件<Suspense>
MemoComponent14memo包装的组件React.memo(Component)
LazyComponent16懒加载组件React.lazy(() => import())
OffscreenComponent22离屏组件内部使用

4.4 双缓冲机制

React使用双缓冲(Double Buffering)技术来实现流畅的更新。这是一种在图形渲染中常用的技术,React将其应用到了Fiber树的管理中。

4.4.1 current树与workInProgress树

React在内存中同时维护两棵Fiber树:

current树:当前屏幕上显示的内容对应的Fiber树

workInProgress树:正在构建的新Fiber树

┌─────────────────────────────────────────────────────────────┐
│                        内存中的两棵树                         │
│                                                             │
│  current树                    workInProgress树              │
│  (当前显示)                    (正在构建)                     │
│                                                             │
│     App                          App'                       │
│      │                            │                         │
│      ▼                            ▼                         │
│     div                          div'                       │
│    /   \                        /   \                       │
│   h1    p                      h1'   p'                     │
│                                                             │
│  每个节点通过alternate指针相互连接                            │
│  current.alternate = workInProgress                         │
│  workInProgress.alternate = current                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

为什么需要两棵树?

  1. 避免中断时的不一致:如果直接修改current树,中断时会导致UI不一致
  2. 快速回滚:如果更新失败,可以直接丢弃workInProgress树
  3. 性能优化:可以复用current树的节点,减少内存分配

4.4.2 alternate指针

每个Fiber节点都有一个alternate指针,指向另一棵树中对应的节点:

javascript
// 文件:packages/react-reconciler/src/ReactFiber.js

function FiberNode(/*...*/) {
  // ...
  this.alternate = null;  // 指向另一棵树的对应节点
  // ...
}

创建workInProgress节点

当开始更新时,React会基于current树创建workInProgress树:

javascript
// 文件:packages/react-reconciler/src/ReactFiber.js
// 行号:340-450(React 19.3.0)

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  
  if (workInProgress === null) {
    // 首次更新:创建新的Fiber节点
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
    
    // 建立双向连接
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    // 复用已有的alternate节点
    workInProgress.pendingProps = pendingProps;
    workInProgress.type = current.type;
    
    // 清除副作用标记
    workInProgress.flags = NoFlags;
    workInProgress.subtreeFlags = NoFlags;
    workInProgress.deletions = null;
  }
  
  // 复制其他属性
  workInProgress.flags = current.flags & StaticMask;
  workInProgress.childLanes = current.childLanes;
  workInProgress.lanes = current.lanes;
  
  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;
  
  // 复制依赖
  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;
  
  return workInProgress;
}

双缓冲的工作流程

1. 初始状态:只有current树
   current树 ──▶ 屏幕显示

2. 开始更新:创建workInProgress树
   current树 ──▶ 屏幕显示
   workInProgress树 ──▶ 正在构建

3. 完成更新:切换指针
   current树 ◀── workInProgress树 ──▶ 屏幕显示
   (变成新的current)

4. 下次更新:复用旧的current树作为workInProgress
   workInProgress树 ◀── current树 ──▶ 屏幕显示
   (复用节点)

示例:双缓冲的完整过程

jsx
function Counter() {
  const [count, setCount] = useState(0);
  return <div>{count}</div>;
}

初始渲染

创建current树:
Counter (count: 0)


 div


 "0"

屏幕显示:0

点击按钮,count变为1

步骤1:创建workInProgress树
current树:                workInProgress树:
Counter (count: 0)  ◀──▶  Counter' (count: 1)
  │                          │
  ▼                          ▼
 div                        div'
  │                          │
  ▼                          ▼
 "0"                        "1"

屏幕仍显示:0

步骤2:完成协调,提交更新
workInProgress树变成新的current树

新current树:
Counter (count: 1)


 div


 "1"

屏幕显示:1

再次点击,count变为2

步骤1:复用旧的current树作为workInProgress
旧current树 (复用):        新current树:
Counter (count: 0)  ◀──▶  Counter (count: 1)
  │                          │
  ▼                          ▼
 div                        div
  │                          │
  ▼                          ▼
 "0"                        "1"
  ↓ 更新为                    ↑ 屏幕显示
Counter (count: 2)


 div


 "2"

步骤2:完成后切换
新current树:
Counter (count: 2)


 div


 "2"

屏幕显示:2

4.4.3 树的切换时机

双缓冲树的切换发生在commit阶段:

javascript
// 简化版 - 文件:packages/react-reconciler/src/ReactFiberWorkLoop.js

function commitRoot(root) {
  const finishedWork = root.finishedWork;
  
  // 执行DOM操作
  commitMutationEffects(finishedWork);
  
  // 切换current指针
  root.current = finishedWork;
  
  // 执行layout effects
  commitLayoutEffects(finishedWork);
}

切换前

FiberRoot

  │ current

旧Fiber树 (屏幕显示)

  │ alternate

新Fiber树 (刚完成协调)

切换后

FiberRoot

  │ current

新Fiber树 (屏幕显示)

  │ alternate

旧Fiber树 (等待复用)

切换只需要修改一个指针,非常高效。这就是双缓冲的优势:原子性切换,要么全部更新,要么全部不更新

双缓冲的优势总结

  1. 中断安全:更新过程可以被中断,不影响当前显示
  2. 快速切换:只需修改一个指针,O(1)时间复杂度
  3. 节点复用:下次更新可以复用旧树的节点,减少内存分配
  4. 回滚能力:如果更新失败,可以直接丢弃workInProgress树

源码追踪

如果你想在源码中追踪双缓冲机制,以下是关键文件:

功能源码文件
创建workInProgresspackages/react-reconciler/src/ReactFiber.js
树的切换packages/react-reconciler/src/ReactFiberWorkLoop.js
FiberRoot定义packages/react-reconciler/src/ReactFiberRoot.js

4.5 示例:Fiber树结构图解

为了更直观地理解Fiber树的结构,让我们通过一个完整的示例来可视化Fiber树。

示例代码

jsx
function App() {
  return (
    <div className="app">
      <Header />
      <Main>
        <Article />
        <Sidebar />
      </Main>
      <Footer />
    </div>
  );
}

function Header() {
  return <header>Header</header>;
}

function Main({ children }) {
  return <main>{children}</main>;
}

function Article() {
  return <article>Article</article>;
}

function Sidebar() {
  return <aside>Sidebar</aside>;
}

function Footer() {
  return <footer>Footer</footer>;
}

Fiber树结构

HostRoot (tag: 3)

  │ child

App (tag: 0, FunctionComponent)

  │ child

div.app (tag: 5, HostComponent)

  │ child

Header (tag: 0) ──sibling──▶ Main (tag: 0) ──sibling──▶ Footer (tag: 0)
  │                            │                           │
  │ child                      │ child                     │ child
  ▼                            ▼                           ▼
header (tag: 5)              main (tag: 5)              footer (tag: 5)
  │                            │                           │
  │ child                      │ child                     │ child
  ▼                            ▼                           ▼
"Header" (tag: 6)           Article (tag: 0)           "Footer" (tag: 6)

                               │ sibling

                            Sidebar (tag: 0)

                               │ child

                            article (tag: 5)

                               │ sibling

                            aside (tag: 5)

                               │ child

                            "Article" (tag: 6)

                               │ sibling (aside的child)

                            "Sidebar" (tag: 6)

遍历顺序

React使用深度优先遍历(DFS)处理Fiber树:

1. HostRoot
2. App
3. div.app
4. Header
5. header
6. "Header" (文本)
7. 回到header,没有sibling,回到Header
8. 回到Header,有sibling Main
9. Main
10. main
11. Article
12. article
13. "Article" (文本)
14. 回到article,有sibling aside
15. aside
16. "Sidebar" (文本)
17. 回到aside,没有sibling,回到main
18. 回到main,没有sibling,回到Main
19. 回到Main,有sibling Footer
20. Footer
21. footer
22. "Footer" (文本)
23. 遍历结束

Fiber节点的详细信息

javascript
// App Fiber
{
  tag: 0,                    // FunctionComponent
  type: App,
  key: null,
  return: hostRootFiber,
  child: divFiber,
  sibling: null,
  memoizedProps: {},
  memoizedState: null,
  alternate: null,           // 首次渲染时为null
}

// div.app Fiber
{
  tag: 5,                    // HostComponent
  type: 'div',
  key: null,
  return: appFiber,
  child: headerFiber,
  sibling: null,
  memoizedProps: {
    className: 'app',
    children: [/*...*/]
  },
  stateNode: <div class="app">...</div>,  // 真实DOM节点
  alternate: null,
}

// Header Fiber
{
  tag: 0,                    // FunctionComponent
  type: Header,
  key: null,
  return: divFiber,
  child: headerElementFiber,
  sibling: mainFiber,        // 下一个兄弟是Main
  memoizedProps: {},
  memoizedState: null,
  alternate: null,
}

// header元素 Fiber
{
  tag: 5,                    // HostComponent
  type: 'header',
  key: null,
  return: headerFiber,
  child: textFiber,
  sibling: null,
  memoizedProps: {
    children: 'Header'
  },
  stateNode: <header>...</header>,
  alternate: null,
}

// "Header"文本 Fiber
{
  tag: 6,                    // HostText
  type: null,
  key: null,
  return: headerElementFiber,
  child: null,
  sibling: null,
  memoizedProps: 'Header',
  stateNode: Text("Header"),
  alternate: null,
}

双缓冲状态下的Fiber树

当发生更新时(例如App的state变化),React会创建workInProgress树:

current树:                          workInProgress树:
HostRoot                            HostRoot'
  │                                   │
  │ alternate ◀──────────────────────▶│ alternate
  ▼                                   ▼
App                                 App'
  │                                   │
  │ alternate ◀──────────────────────▶│ alternate
  ▼                                   ▼
div.app                             div.app'
  │                                   │
  │ alternate ◀──────────────────────▶│ alternate
  ▼                                   ▼
...                                 ...

每个节点都通过alternate指针连接到另一棵树的对应节点

本章小结

本章深入探讨了React的Fiber架构,主要内容包括:

  1. Fiber的概念:Fiber既是一种架构,也是一种数据结构。它使React能够实现可中断的渲染和优先级调度。

  2. Fiber节点属性

    • 静态属性:tag、type、key标识节点类型
    • 关系属性:return、child、sibling构成树结构
    • 状态属性:memoizedState、memoizedProps保存组件状态
    • 副作用属性:flags、subtreeFlags标记需要执行的操作
  3. WorkTag类型:React定义了30多种WorkTag,常用的包括FunctionComponent、HostComponent、HostText、SuspenseComponent等。

  4. 双缓冲机制:React维护current和workInProgress两棵树,通过alternate指针连接。更新时在workInProgress树上工作,完成后原子性切换。

理解Fiber架构是深入React源码的基础。在下一章中,我们将学习React的调度系统,了解React如何管理任务优先级和时间切片。


思考题

  1. 为什么Fiber使用链表结构(child、sibling)而不是数组来保存子节点?这种设计有什么优势?

  2. 双缓冲机制中,为什么要复用旧树的节点而不是每次都创建新节点?这样做的性能优势在哪里?

  3. 如果一个组件在更新过程中被中断,React如何确保下次继续时能找到正确的位置?

  4. Suspense组件的Fiber节点如何与Offscreen组件配合工作?为什么需要Offscreen组件?

  5. 在你的项目中,如何利用Fiber架构的特性来优化性能?例如,如何合理使用React.memo和useMemo?