React 设计原理 - 卡颂

《React 设计原理》致力于剖析 React 设计理念与实现原理,基于 React 18 源 码讲解。全书分为 3 篇,第 1 篇为理念篇(第 1 章~第 2 章),讲解 React 在主流前端框架中的定位与设计理念;第 2 篇为架构篇(第 3 章~第 5 章),讲解 React 架构中的 3 个阶段——render、commit、schedule,以及如何在架构中践行设计理念;第 3 篇为实现篇(第 6 章~第 8 章),贯穿 React 架构中的 3 个阶段,讲解具体 API 的实现细节。
关于作者
卡颂 是前端技术专家、技术作家:
- 前端技术专家:深入研究和实践 React 技术栈
- 技术博主:长期在个人博客和各大技术平台分享前端技术文章
- 开源贡献者:积极参与开源社区,贡献代码和技术内容
- 技术作家:擅长将复杂的技术原理以通俗易懂的方式讲解
卡颂以其"深入源码、剖析原理"的写作风格著称,他不满足于表面的 API 使用,而是深入源码层面,揭示 React 设计背后的原理和思想。
核心内容
1. React 的设计理念
React 的核心设计理念:
1. 声明式编程 (Declarative)
- 描述 UI 应该是什么样子
- 而非命令式地操作 DOM
- 代码更易读、更易维护
// 命令式
const div = document.createElement('div');
div.textContent = 'Hello World';
document.body.appendChild(div);
// 声明式
<div>Hello World</div>
2. 组件化 (Component-Based)
- UI 拆分为独立可复用的组件
- 每个组件管理自己的状态
- 组件组合构建复杂 UI
3. 单向数据流 (One-Way Data Flow)
- 数据从父组件流向子组件
- props 向下传递
- 状态变化触发重新渲染
4. 函数式编程思想 (Functional Programming)
- 纯函数:相同输入 = 相同输出
- 不可变数据
- 副作用隔离
2. Fiber 架构
Fiber 架构的核心:
1. 为什么需要 Fiber?
- React 15 及之前:递归是不可中断的
- 大型组件树:可能导致主线程阻塞
- 用户体验:卡顿、掉帧
2. Fiber 是什么?
- 数据结构:表示组件节点
- 执行单元:可中断、可恢复的最小工作单元
- 链表结构:替代递归遍历
3. Fiber 节点结构
type FiberNode = {
type: 组件类型,
props: 属性,
return: 父节点,
child: 子节点,
sibling: 兄弟节点,
effectTag: 副作用标记,
next: 下一个有副作用的节点
}
4. 双缓冲机制 (Double Buffering)
- current Fiber: 当前屏幕显示
- workInProgress Fiber: 正在构建的新树
- 完成后一次性提交
3. Render 阶段
Render 阶段的工作流程:
1. 触发更新
- setState()
- useState() setter
- 父组件重新渲染
2. 创建 Fiber 树
- 从根节点开始
- 深度优先遍历
- 构建完整的 Fiber 树
3. Diff 算法
- 比较新旧 Fiber 树
- 标记需要更新的节点
- 三种 Diff 策略:
* 同类型:继续比较子节点
* 不同类型:删除旧节点,创建新节点
* key 值:优化列表渲染
4. 收集副作用
- 需要插入的节点
- 需要更新的节点
- 需要删除的节点
5. 可中断性
- 使用 requestIdleCallback
- 时间分片
- 高优先级任务可插队
4. Commit 阶段
Commit 阶段的工作流程:
1. 不可中断
- 必须同步执行完成
- 避免 UI 不一致
2. 三个子阶段
(1) Before Mutation
- 读取 Fiber 树
- 记录 DOM 状态
(2) Mutation
- 应用 DOM 操作
- 插入、更新、删除
(3) Layout
- 调用 useEffect 回调
- 执行使用端效果
3. 提交到屏幕
- 更新 refs
- 触发生命周期
- 执行副作用回调
4. 优先级调度
- 用户交互 > 动画 > 数据获取 > 空闲任务
- Lane 模型管理优先级
5. Schedule 调度
调度系统的核心概念:
1. 为什么要调度?
- 任务有轻重缓急
- 避免低优先级任务阻塞高优先级
- 优化用户体验
2. Lane 模型
- 用位运算表示优先级
- 不同 Lane 代表不同优先级
- 支持批量更新和合并
const SyncLane = 0b00001; // 同步任务
const InputContinuousLane = 0b00100; // 用户输入
const DefaultLane = 0b10000; // 默认优先级
3. 调度策略
- 高优先级:立即执行
- 中优先级:短时间延迟
- 低优先级:空闲时执行
4. 饥饿问题处理
- 低优先级任务等待过久会升级
- 避免永远得不到执行
6. Hooks 实现原理
Hooks 的工作原理:
1. 链表结构
- 每个 Hook 是一个节点
- 按调用顺序连接
- 依赖顺序的一致性
2. useState 实现
function useState(initialState) {
// 从链表中获取或创建 Hook
const hook = getHook();
// 如果是初次渲染
if (!hook.hasState) {
hook.state = initialState;
hook.hasState = true;
}
// 返回 [state, setState]
return [hook.state, createSetter(hook)];
}
3. useEffect 实现
- 保存依赖数组
- 比较依赖是否变化
- 决定是否需要执行
4. 规则与限制
- 只能在顶层调用
- 不能在条件语句中调用
- 依赖数组决定执行时机
7. Concurrent 特性
Concurrent 并发生态:
1. 可中断渲染
- Render 阶段可中断
- 高优先级任务优先
- 恢复时复用已完成工作
2. 并发更新
- 多次更新合并处理
- 批量更新优化
- 避免不必要的重渲染
3. Suspense
- 等待异步数据
- 显示 fallback UI
- 自动重试机制
4. useTransition
- 标记过渡更新
- 低优先级更新
- 不影响用户交互
5. useDeferredValue
- 延迟更新值
- 优先渲染新输入
- 再更新派生内容
经典摘录
React 的设计目标:构建可预测、易于理解的 UI。
声明式的力量在于:描述"是什么",而非"怎么做"。
Fiber 架构的核心:将不可中断的递归,变为可中断的链表遍历。
单向数据流让应用行为更可预测。
Diff 算法的本质:找到最小更新路径。
调度的本质:在合适的时间,做合适的事。
Hooks 让函数组件拥有了"记忆"能力。
Concurrent 的意义:让 UI 响应用户优先。
读书心得
《React 设计原理》是一本深入 React 源码的书籍。读完之后,我对 React 的理解从"如何使用"提升到了"为什么这样设计"的层面 。
Fiber 架构的讲解让我印象深刻。之前只知道 Fiber 解决了卡顿问题,但不清楚具体原理。通过阅读源码分析,理解了 Fiber 如何将递归变为可中断的链表遍历,这是多么巧妙的设计。
双缓冲机制的设计思想源于图形学,React 将其应用到 UI 更新中。current 树表示当前屏幕显示,workInProgress 树在后台构建,完成后一次性提交。这种设计保证了 UI 的一致性。
Lane 优先级模型解决了任务调度问题。不同的更新有不同的优先级,用户交互应该优先处理,数据获取可以稍后执行。这种细粒度的控制让 React 更加灵活。
Hooks 实现原理的剖析解开了我的很多疑惑。为什么 Hooks 不能条件调用?因为底层是链表结构,调用顺序必须一致。为什么需要依赖数组?因为需要比较依赖是否变化来决定是否执行。
这本书适合有一定 React 使用经验的开发者。如果你对 React 的工作原理感到好奇,想深入理解其设计思想,这本书值得一读。