Skip to content
目录

关键设计决策和原因

1. 为什么选择 Fiber 架构?

1.1 问题背景

React 15 及之前的版本使用递归渲染,存在以下问题:

  1. 不可中断:渲染过程一旦开始就不能中断
  2. 阻塞主线程:长时间渲染会阻塞主线程
  3. 用户体验差:用户交互会被延迟

1.2 解决方案

Fiber 架构通过以下方式解决这些问题:

  1. 可中断渲染:将渲染工作分成多个小任务,可以暂停和恢复
  2. 优先级调度:根据优先级决定先执行哪些任务
  3. 时间切片:将长时间任务分成多个小任务

1.3 设计原因

根据 Fiber Principles

  • 增量渲染:支持将渲染工作分成多个小任务
  • 优先级调度:支持根据优先级调度任务
  • 错误边界:支持错误边界捕获更新错误
  • 向后兼容:保持与现有 API 的兼容性

2. 为什么使用双缓冲?

2.1 问题背景

每次更新都创建新的 Fiber 树会导致:

  1. 内存分配:频繁的内存分配和回收
  2. 性能问题:GC 压力大
  3. 无法复用:无法复用之前的节点

2.2 解决方案

使用双缓冲技术:

  • Current Tree:当前显示在屏幕上的树
  • WorkInProgress Tree:正在构建的新树
  • alternate 字段:两个树之间的连接

2.3 设计原因

  1. 内存优化:复用节点,减少内存分配
  2. 性能优化:减少 GC 压力
  3. 一致性:保证更新的一致性

3. 为什么使用 Lanes 而不是 ExpirationTime?

3.1 ExpirationTime 的问题

React 16 使用 ExpirationTime 管理优先级,存在以下问题:

  1. 精度问题:ExpirationTime 是时间戳,精度有限
  2. 优先级冲突:多个更新可能有相同的 ExpirationTime
  3. 难以扩展:难以支持更多的优先级级别

3.2 Lanes 的优势

React 18 使用 Lanes(车道)系统:

  1. 位掩码:使用位掩码表示优先级,支持多个优先级
  2. 高效操作:位运算比时间戳比较快
  3. 易于扩展:可以轻松添加新的优先级级别

3.3 设计原因

根据 Lanes RFC

  • 更好的优先级管理:支持多个优先级级别
  • 更高效的比较:位运算比时间戳比较快
  • 更灵活的调度:可以更灵活地调度任务

4. 为什么 Hooks 必须按顺序调用?

4.1 问题背景

Hooks 通过每次渲染的调用顺序对应 Fiber 上链表槽位;条件/循环中多调或少调会导致状态串线(React 19 的 use 是例外)。详见 04-Hooks §4

4.2 解决方案

强制 Hooks 按顺序调用:

  1. Rules of Hooks:Hooks 必须在顶层调用
  2. 开发时检查:开发模式下检查 Hooks 调用顺序
  3. 错误提示:提供清晰的错误信息

4.3 设计原因

根据 Hooks RFC

  • 简单实现:通过调用顺序识别 Hooks,实现简单
  • 性能优化:不需要额外的标识符
  • 易于理解:规则简单明了

5. 为什么使用链表存储 Hooks?

5.1 问题背景

Hooks 数量在运行时确定,不能使用固定大小的数组。

5.2 解决方案

使用链表存储 Hooks:

javascript
type Hook = {
  memoizedState: any,
  next: Hook | null,
};

5.3 设计原因

  1. 动态数量:支持动态数量的 Hooks
  2. 顺序稳定:保持 Hooks 的调用顺序
  3. 高效查找:通过遍历链表访问 Hooks

6. 为什么 Diff 算法只比较同层节点?

6.1 问题背景

跨层级比较的复杂度是 O(n³),性能很差。

6.2 解决方案

只比较同一层级的节点:

  1. 同层比较:只比较同一层级的节点
  2. 类型检查:类型不同直接替换
  3. key 优化:使用 key 快速识别节点

6.3 设计原因

  1. 性能优化:同层比较的复杂度是 O(n)
  2. 实际场景:大多数情况下组件不会跨层级移动
  3. 简单实现:实现简单,易于维护

7. 为什么使用 Virtual DOM?

7.1 问题背景

直接操作 DOM 很昂贵:

  1. 重排和重绘:每次 DOM 操作都可能触发重排和重绘
  2. 跨平台:不同平台的 DOM API 不同
  3. 批处理困难:难以批量处理多个更新

7.2 解决方案

使用 Virtual DOM:

  1. 抽象层:Virtual DOM 是 DOM 的抽象
  2. 批量更新:可以将多个更新合并
  3. 跨平台:同一套 Virtual DOM 可以渲染到不同平台

7.3 设计原因

  1. 性能优化:减少 DOM 操作次数
  2. 跨平台:支持 Web、Native、Canvas 等平台
  3. 开发体验:提供声明式的开发方式

8. 为什么使用 Scheduler?

8.1 问题背景

浏览器是单线程的,长时间任务会阻塞主线程。

8.2 解决方案

使用 Scheduler 调度任务:

  1. 时间切片:将长时间任务分成多个小任务
  2. 优先级调度:根据优先级调度任务
  3. 让出控制权:在适当时机让出控制权

8.3 设计原因

  1. 用户体验:避免阻塞主线程
  2. 响应性:优先处理用户交互
  3. 性能优化:充分利用浏览器的时间切片

9. 为什么引入 Server Components?

9.1 问题背景

客户端 JavaScript 包越来越大,影响性能。

9.2 解决方案

引入 Server Components:

  1. 减少包大小:Server Components 不在客户端
  2. 直接访问数据:可以直接访问数据库和文件系统
  3. 更好的性能:减少网络请求和客户端计算

9.3 设计原因

  1. 性能优化:减少客户端 JavaScript 包大小
  2. 开发体验:简化数据获取逻辑
  3. 未来方向:是 React 的未来方向

10. 为什么使用 Suspense?

10.1 问题背景

异步数据获取和代码分割需要单独处理。

10.2 解决方案

使用 Suspense 统一处理:

  1. 统一 API:统一的异步处理 API
  2. 更好的 UX:提供更好的加载体验
  3. 简化代码:简化异步数据获取代码

10.3 设计原因

  1. 统一处理:统一处理异步数据和代码分割
  2. 更好的 UX:提供更好的加载体验
  3. 简化代码:简化异步数据获取代码

11. 总结

React 的关键设计决策:

  1. Fiber 架构:支持可中断渲染和优先级调度
  2. 双缓冲:优化内存使用和性能
  3. Lanes 系统:更好的优先级管理
  4. Hooks:简化状态管理和逻辑复用
  5. Virtual DOM:跨平台和性能优化
  6. Scheduler:时间切片和优先级调度
  7. Server Components:减少包大小和提升性能
  8. Suspense:统一异步处理

这些设计决策都是为了提供更好的性能、开发体验和用户体验。

MIT License.