React 源码面试冲刺 - Day 4

8 阅读7分钟

React 源码面试冲刺 - Day 4

日期:2026-03-19 主题:Fiber 架构


📖 Day 4 核心内容:Fiber 架构

👴 老大爷能听懂版

Fiber = React 的"重新设计的大脑"

旧架构(Stack Reconciler)新架构(Fiber)
递归更新,无法中断可中断、可恢复
一旦开始必须完成可以"先歇歇"再继续
主线程被阻塞空闲时间接着干
同步进行异步进行

Fiber 的核心思想:把工作拆成小块!

想象一下:

  • 旧版本:你要搬家,一口气把所有东西搬完,累死
  • Fiber 版本:每次搬一点,搬累了休息一下喝口水���然后再搬

Fiber 节点是什么?

  • 每个 React 元素对应一个 Fiber 节点
  • Fiber 节点是链表结构,不是树!
  • 可以无限延伸(因为是链表)
<div>                    FiberNode
  <h1>Title</h1>    →   child → child → child
  <p>Text</p>       →   child
</div>

Fiber 的三要素:

  1. 是什么: 一个链表节点,代表一个工作单元
  2. 从哪里来: React Element 转换而来
  3. 到哪里去: 最终变成 DOM 节点

💻 专业开发者版

Fiber 节点结构:

type Fiber = {
  // 身份标识
  tag: WorkTag,              // 组件类型:FunctionComponent、ClassComponent...
  key: null | string,        // 识别符
  type: any,                 // 组件定义(函数/类/字符串)
  
  // 链表结构
  return: Fiber | null,      // 父节点
  child: Fiber | null,       // 第一个子节点
  sibling: Fiber | null,    // 下一个兄弟节点
  index: number,            // 在兄弟中的位置
  
  // 状态
  pendingProps: any,        // 新的 props
  memoizedProps: any,       // 渲染用的 props
  memoizedState: any,       // 组件状态(hooks 链表)
  
  // 更新相关
  updateQueue: mixed,       // 待处理的更新队列
  alternate: Fiber | null, // 旧 Fiber(用于对比)
  
  // 副作用
  flags: Flags,             // 需要执行的副作用
  subTreeFlags: Flags,      // 子树的副作用
  deletions: Fiber[],       // 需要删除的节点
  
  // 调试
  _debugID: number,
  _debugSource: any,
}

Fiber 的工作流程:

Render 阶段(可中断)
↓
1. beginWork() → 从根节点向下遍历
2. diffFiber() → 对比新旧 Fiber
3. completeWork() → 完成当前分支

Commit 阶段(不可中断)
↓
1. BeforeMutation → DOM 更新前
2. Mutation → 实际 DOM 操作
3. Layout → 布局后 effect
4. Passive → useEffect 触发

Fiber 的核心优势:

优势说明
可中断优先级高的任务可以插队
可恢复中断后能接着干,不丢失进度
优先级调度紧急任务(如输入)优先处理
并发支持同时处理多个 Fiber 树

React 的优先级机制:

// 优先级从高到低
export const PriorityLevel = {
  ImmediatePriority: 1,     // 最高:点击、输入
  UserBlockingPriority: 2,  // 用户Blocking:滚动
  NormalPriority: 3,        // 普通:数据加载
  LowPriority: 4,           // 低:内容刷新
  IdlePriority: 5,          // 最低:预加载
};

key 和 alternate:

  • alternate:双缓冲技术,旧 Fiber 保留,上一份和这一份对比
  • 这样可以实现"无缝"更新,不会出现页面闪烁

💪 面试高频问题

问题答案要点
什么是 Fiber?React 16 引入的新协调引擎,链表结构的工作单元
Fiber 解决了什么问题?主线程阻塞、无法优先级调度、不能中断恢复
Fiber 和 Virtual DOM 的关系?Fiber 是 Virtual DOM 的升级版,从树变链表
React 如何保证页面不卡顿?任务拆分 + 时间片 + requestIdleCallback
Fiber 的 Render 和 Commit 区别?Render 可中断、Commit 必须同步完成
Fiber 节点的数据结构?child/sibling/return 链表,alternate 双缓冲

📊 Day 4 面试能力评估

Day 1-4 学完能答:

  • ✅ HTML 基础概念
  • ✅ React 核心 API
  • ✅ JSX 转换原理
  • ✅ Hooks 原理
  • Fiber 架构 ← 今日新增

还需要 Day 5+:

  • ❌ Diff 算法
  • ❌ 调度器原理
  • ❌ 并发模式
  • ❌ 状态管理原理

💪 今日自测

  1. 为什么 Fiber 用链表而不是树?
  2. Fiber 的 Render 阶段和 Commit 阶段有什么区别?
  3. React 怎么做到���输入不卡顿的?(优先级机制)
  4. alternate 在 Fiber 中起什么作用?

📝 详细答案

1. 为什么 Fiber 用链表而不是树?

核心原因:链表支持中断和恢复,树上不行

对比项树结构链表结构
遍历方式递归(无法中断)循环(可以暂停)
内存占用固定、连续分散、不连续
中断恢复困难(不知道停在哪)简单(保存指针就行)
父子关系严格树形可灵活指向

具体解释:

旧架构:树 + 递归
function walk(node) {
  process(node);      // 处理当前
  node.children.forEach(walk); // 递归子节点
  // ❌ 一旦开始根本无法停止,必须一口气跑完
}

新架构:链表 + 循环
function workLoop() {
  while (nextUnitOfWork) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    // ✅ 每次处理一个 Fiber 节点,处理完可以暂停
    if (shouldYield()) {
      return; // 让出主线程,下次继续
    }
  }
}

链表结构的优势:

  • child:第一个子节点
  • sibling:下一个兄弟节点
  • return:父节点

这样可以从任意节点"断掉",下次继续从"断掉"的地方开始。


2. Fiber 的 Render 阶段和 Commit 阶段有什么区别?

阶段RenderCommit
可中断?✅ 可以❌ 必须同步完成
执行方式异步同步
主要工作计算差异,收集副作用实际操作 DOM
耗时可能很长(可拆分)必须尽快完成

Render 阶段(可中断)���

// 伪代码
function renderRoot(root) {
  workInProgress = root;
  
  while (workInProgress) {
    // 处理一个 Fiber 节点
    workInProgress = performUnitOfWork(workInProgress);
    
    // 检查是否要让出主线程
    if (shouldYield()) {
      // 👻 中断!保存状态,下次继续
      return;
    }
  }
  
  // 全部处理完,进入 Commit
  commitRoot();
}

Render 阶段内部流程:

beginWork()
  ↓
  根据 Fiber 类型的处理
  ↓
  reconcileChildren() → 对比 children
  ↓
completeWork()

Commit 阶段(不可中断):

function commitRoot(root) {
  // 1️⃣ DOM 更新前
  commitBeforeMutationEffects();
  
  // 2️⃣ 实际 DOM 操作(可能会阻塞)
  commitMutationEffects();
  
  // 3️⃣ 布局后 effects(同步)
  commitLayoutEffects();
  
  // 4️⃣ useEffect(异步)
  commitPassiveEffects();
}

为什么 Commit 不能中断?

  • 因为用户能看到页面变化了,必须保证一致性
  • 如果中断了,页面可能显示一半,用户体验极差

3. React 怎么做到让输入不卡顿的?(优先级机制)

核心:高优先级任务可以打断低优先级任务

// React 的优先级定义(简化版)
const priorities = {
  ImmediatePriority: 1,    // 🔴 最急:用户输入、点击
  UserBlockingPriority: 2, // 🟠 较急:滚动
  NormalPriority: 3,       // 🟡 普通:网络请求、渲染
  LowPriority: 4,          // 🟢 不急:后台刷新
  IdlePriority: 5,         // ⚪ 最闲:预加载
};

工作流程:

用户输入 → 触发更新
     ↓
React 分配 ImmediatePriority(最高)
     ↓
中断当前低优先级任务
     ↓
立即处理用户输入
     ↓
处理完继续之前的低优先级任务

实际例子:

场景:正在渲染一个大列表,用户突然点击按钮

1. 本来在渲染列表(NormalPriority)
2. 用户点击按钮 → 触发更新(ImmediatePriority)
3. React 检测到更高优先级,中断渲染
4. 立即处理按钮点击 → 更新 UI
5. 处理完再回头继续渲染列表

怎么实现的?

// 每次处理完一个 Fiber,检查是否要让行
function workLoop() {
  while (nextUnitOfWork) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    
    // 关键:如果当前任务的优先级 < 新任务的优先级
    // 让出主线程!
    if (currentPriority < nextPriority) {
      scheduleCallback(flushWork); // 下次继续
      return;
    }
  }
}

总结:Fiber + 优先级 = 输入不卡顿


4. alternate 在 Fiber 中起什么作用?

alternate = 双缓冲,实现"无缝"更新

// 每个 Fiber 都有 alternate 指向旧版本
fiber.alternate = oldFiber; // 旧 Fiber

双缓冲技术:

当前 Fiber 树(workInProgress)      Fiber 树(current)
┌─────────────────┐               ┌─────────────────┐
 root                            root            
   child: A       ─── alternate ───→ child: A     
     child: B                      child: B     
       child: C                      child: C   
└─────────────────┘               └─────────────────┘

为什么需要 alternate?

  1. 对比差异:新 Fiber 和旧 Fiber 一一对比,找出哪里要更新
  2. 复用节点:如果对比发现没变化,直接复用旧节点(节省内存)
  3. 快照备份:如果更新中途失败,可以回滚到旧状态

复用示例:

// 对比新旧 Fiber
function reconcileChildren(current, newChildren) {
  return newChildren.map((child, index) => {
    const oldChild = current?.[index];
    
    // 关键:判断是否可以复用
    if (canReuseScheduleUnitOfWork(oldChild, child)) {
      // ✅ 复用!只要标记_update
      return {
        ...oldChild,
        pendingProps: child.props, // 更新属性
      };
    }
    
    // ❌ 不能复用,创建新的
    return createFiber(child);
  });
}

实际效果:

场景:只改了按钮文字

旧 Fiber 树:
  <button>OK</button>  → 保存

新 Fiber 树:
  <button>Submit</button> → 只标记 textUpdate

结果:
- 其他没改的节点完全复用,不重新创建
- ⚡ 性能提升显著!

完整流程:

更新触发
    ↓
创建 workInProgress 树(拷贝 current)
    ↓
遍历同时对比 current 和 workInProgress
    ↓
标记需要更新的节点(flags)
    ↓
Commit 阶段只更新标记的节点
    ↓
完成!workInProgress 变成新的 current

恭喜 Day 4 学完!Fiber 核心在于:链表可中断 + 优先级可插队 + 双缓冲复用 🎉