如何学习react源码
学习 React 源码时,直接通读全部代码容易陷入细节、失去方向。React 源码的核心逻辑围绕「组件渲染与更新」展开,因此建议从 5个核心模块 切入,按「基础原理→核心流程→关键特性」的顺序递进,逐步建立完整的认知框架。以下是每个模块的核心内容、学习重点及理解技巧:
一、先搞懂:React 源码的「基础架构」(前置认知)
在深入具体模块前,需要先明确 React 的整体分层设计——这是理解后续模块协作的前提。React 源码本质是「分层职责清晰的协作系统」,核心分为3层:
| 分层 | 核心职责 | 关键模块/文件 | 为什么先学? |
|---|---|---|---|
| Scheduler | 「任务调度」:决定什么时候执行什么任务 | scheduler/ 目录 |
所有渲染、更新任务的“入口调度器” |
| Reconciler | 「协调器」:找差异(Diff 算法)、标记更新 | react-reconciler/ 目录 |
连接“调度”与“渲染”的核心逻辑 |
| Renderer | 「渲染器」:将差异渲染到真实平台(DOM/原生) | react-dom/、react-native/ |
不同平台的“最终执行层”,DOM 场景最常用 |
学习重点:记住「调度→协调→渲染」的核心流程,理解各层的边界(比如 Scheduler 不关心“渲染什么”,只关心“什么时候执行”;Renderer 不关心“差异怎么来”,只关心“怎么渲染差异”)。
二、核心模块1:Scheduler(任务调度)—— React 的“大脑”
React 不是“同步执行所有更新”,而是通过 Scheduler 实现「优先级调度」(比如:用户输入的优先级 > 列表渲染的优先级),避免卡顿。这是 React 16+ 引入 Fiber 架构的核心支撑。
1. 核心职责
- 管理「任务优先级」:定义 5 级优先级(如
ImmediatePriority立即执行,IdlePriority空闲时执行); - 调度「任务执行时机」:利用浏览器的
requestIdleCallback(降级用setTimeout),在浏览器空闲时执行低优先级任务; - 中断与恢复:支持任务执行到一半被高优先级任务打断,后续再恢复执行(Fiber 架构的基础)。
2. 关键代码与概念
- 任务队列:用「小顶堆」存储任务(按优先级排序,每次取优先级最高的任务);
unstable_scheduleCallback:调度任务的入口函数,接收“优先级”和“任务函数”,返回任务 ID;unstable_cancelCallback:取消已调度但未执行的任务。
3. 学习技巧
不用纠结堆的实现细节,重点理解「为什么需要调度」:如果同步执行大量更新(比如渲染1000个列表项),会阻塞浏览器的 UI 线程,导致用户点击、输入无响应。Scheduler 通过“切片执行”(把长任务拆成多个短任务)解决这个问题。
三、核心模块2:Reconciler(协调器)—— React 的“差异计算器”
Reconciler 的核心是「Fiber 架构」和「Diff 算法」:它接收 Scheduler 调度的任务,遍历组件树找到“需要更新的部分”,并标记更新类型(如新增、删除、修改),最后把标记交给 Renderer 渲染。
1. 核心职责
- 构建/更新「Fiber 树」:用 Fiber 节点替代传统的虚拟 DOM 树,支持任务中断与恢复;
- 执行「Diff 算法」:对比新旧 Fiber 节点,计算差异(比如组件 props 变了吗?子节点顺序变了吗?);
- 标记「更新优先级」:给需要更新的 Fiber 节点打上“优先级标签”(如
UpdatePriority),决定渲染顺序。
2. 关键代码与概念
- Fiber 节点结构:每个组件对应一个 Fiber 节点,核心属性包括:
type:组件类型(如div、FunctionComponent);stateNode:真实 DOM 节点(或类组件实例);child/sibling/return:构建 Fiber 树的链表指针(替代虚拟 DOM 的递归树);pendingProps/memoizedProps:新/旧 props,用于 Diff 对比;effectTag:更新标记(如Placement新增、Update修改、Deletion删除)。
- 「双 Fiber 树」机制:
current树:当前页面渲染的 Fiber 树(“旧树”);workInProgress树:正在构建的 Fiber 树(“新树”);- 构建完
workInProgress树后,通过「树切换」(current = workInProgress)完成更新,避免页面闪烁。
- Diff 算法规则(重点理解“同级对比”):
- 只对比「同层级」节点,不跨层级对比(减少计算量);
- 先按「key + type」匹配节点,匹配成功则复用,失败则删除旧节点、创建新节点;
- 列表渲染必须加
key,否则会导致节点错误复用(比如删除列表第一项时,后面的项被错误更新)。
3. 学习技巧
- 画一张 Fiber 树的结构图(用
child/sibling/return连接节点),理解“链表遍历”如何替代“递归遍历”(递归无法中断,链表可以); - 用简单案例测试 Diff 逻辑(比如给列表添加/删除一项),观察
effectTag的变化。
四、核心模块3:Renderer(渲染器)—— React 的“执行者”
Reconciler 只负责“找差异、打标记”,真正把差异渲染到页面的是 Renderer(比如 react-dom 负责浏览器 DOM,react-native 负责原生组件)。我们以最常用的 react-dom 为例。
1. 核心职责
- 「提交更新」:接收 Reconciler 标记的
effectTag,执行真实 DOM 操作(如createElement、appendChild、removeChild); - 「处理副作用」:执行组件的
useEffect、useLayoutEffect等副作用(useLayoutEffect在 DOM 更新后同步执行,useEffect在浏览器绘制后异步执行); - 「事件处理」:统一管理 React 合成事件(比如
onClick),解决浏览器事件兼容问题。
2. 关键代码与概念
commitRoot函数:渲染的“提交入口”,分3个阶段执行:commitBeforeMutationEffects:执行useLayoutEffect的“销毁函数”(更新前);commitMutationEffects:执行 DOM 操作(新增/删除/修改节点);commitLayoutEffects:执行useLayoutEffect的“回调函数”,并更新current树。
- 合成事件系统:
- React 不是把事件绑定在具体 DOM 节点上,而是绑定在
document上(事件委托); - 通过
SyntheticEvent封装原生事件,实现跨浏览器兼容(比如e.preventDefault()在各浏览器行为一致)。
- React 不是把事件绑定在具体 DOM 节点上,而是绑定在
3. 学习技巧
- 打断点调试
commitRoot流程:在 React 源码中找到commitRoot函数,在3个阶段分别加断点,观察 DOM 变化和副作用执行顺序; - 区分
useEffect和useLayoutEffect:前者在“浏览器绘制后”执行(不会阻塞渲染),后者在“DOM 更新后、绘制前”执行(会阻塞渲染,适合需要操作 DOM 后立即获取布局的场景)。
五、核心模块4:Hooks 系统—— 函数组件的“状态管理器”
React 16.8 引入 Hooks 后,函数组件可以拥有状态和副作用。Hooks 系统的核心是「如何在函数组件的每次渲染中,正确保存/读取状态、副作用等信息」。
1. 核心职责
- 管理 Hooks 数据:将
useState/useEffect等 Hooks 的数据存储在 Fiber 节点上; - 保证 Hooks 顺序:通过“链表”存储 Hooks,要求组件内 Hooks 不能在条件语句中使用(否则顺序会乱);
- 触发 Hooks 更新:比如
setState时,标记对应的 Fiber 节点为“待更新”,触发重新渲染。
2. 关键代码与概念
- Hooks 存储位置:每个函数组件的 Fiber 节点上,有
memoizedState属性,用于存储 Hooks 链表(每个 Hook 是链表的一个节点); - Hook 节点结构(以
useState为例):const hook = { memoizedState: 0, // 当前状态值(如 useState(0) 的 0) baseState: 0, // 基础状态(处理并发更新时用) queue: { // 更新队列(存储 setXXX 触发的更新) pending: null // 待执行的更新链表 }, next: null // 下一个 Hook 节点 }; useState原理:- 首次渲染:创建 Hook 节点,存入 Fiber 的
memoizedState,返回[state, setState]; - 后续渲染:从 Fiber 中读取已有的 Hook 节点,返回最新的
[state, setState]; setState触发:将更新任务加入 Hook 的queue,并调度 Reconciler 执行更新。
- 首次渲染:创建 Hook 节点,存入 Fiber 的
3. 学习技巧
- 理解“为什么 Hooks 不能在条件语句中使用”:比如
if (flag) useState(1),如果flag从true变为false,后续渲染时 Hooks 链表的顺序会错乱,导致读取到错误的状态; - 手动模拟
useState:用一个变量存储状态,一个函数修改状态并触发重新执行,理解其本质是“状态的持久化存储”。
六、核心模块5:Concurrent Mode(并发模式)—— React 的“高级特性”
Concurrent Mode 是 React 18 引入的核心特性,允许 React “同时处理多个更新”(比如一边渲染列表,一边响应用户输入),本质是 Scheduler + Reconciler 协作的高级体现。
1. 核心职责
- 支持「可中断的渲染」:低优先级任务(如列表渲染)执行到一半时,若有高优先级任务(如用户点击),可以暂停低优先级任务,先执行高优先级任务;
- 支持「 Suspense 组件」:等待异步数据加载时(如
fetch、React.lazy),显示 fallback 内容,数据加载完成后再渲染组件; - 支持「 Transitions 过渡更新」:标记某些更新为“过渡更新”(如
startTransition),优先级低于用户交互,避免卡顿。
2. 关键代码与概念
useTransitionHook:区分“紧急更新”(如输入框内容)和“过渡更新”(如根据输入筛选列表),过渡更新可被中断;- Suspense 原理:当组件读取“未就绪的异步数据”时(如
useHook 读取 pending 的 Promise),React 会抛出一个 Promise,Suspense 组件捕获这个 Promise,显示 fallback,直到 Promise resolve 后再重新渲染组件; - 并发更新的标记:通过
lane(车道模型)标记任务优先级,不同优先级的任务对应不同的“车道”,高优先级车道的任务会优先执行。
3. 学习技巧
- 先理解“并发”不是“并行”:JavaScript 是单线程,并发是“通过任务切换实现的伪并行”,核心是“中断与恢复”;
- 用 Suspense + React.lazy 做一个简单的路由懒加载案例,观察组件加载时的 fallback 显示逻辑,再去源码中找
throwException相关代码,理解 Promise 捕获机制。
总结:学习顺序与避坑指南
1. 推荐学习顺序
- 基础架构:明确 Scheduler/Reconciler/Renderer 分层;
- Reconciler:先搞懂 Fiber 树和 Diff 算法(最核心的渲染逻辑);
- Scheduler:理解任务调度如何支撑 Fiber 中断;
- Renderer(react-dom):看 DOM 操作和副作用执行流程;
- Hooks 系统:结合 Fiber 节点的
memoizedState理解状态存储; - Concurrent Mode:最后学高级特性,依赖前面的基础。
2. 避坑指南
- 不要一开始就看全部代码:React 源码量大(约 10 万行),先聚焦上述核心模块,忽略边缘特性(如
react-test-renderer、服务器端渲染); - 用“调试+案例”代替“死读代码”:比如写一个简单的
useState组件,打断点看 Hooks 如何存储;写一个列表组件,看 Diff 如何执行; - 参考官方文档和社区解析:React 官方有「React 设计原理」文档,社区有「Dan Abramov 的博客」(React 核心成员)、「React 源码解析系列」等,可辅助理解。
通过以上模块的学习,能逐步建立 React 从“任务调度”到“DOM 渲染”的完整逻辑链,后续再深入其他特性(如 Redux 集成、SSR)会更轻松。
更多推荐
所有评论(0)