读前端源码解析仓库:React、Vue 与微前端的学习路线
本文目录
把 React render/commit、Fiber、Vue 响应式、Redux/SWR 服务端缓存和微前端边界整理成版本可复核的源码阅读实践。
本文围绕 KieSun/awesome-frontend-source-interpretation 做原创工程整理。项目来自 腾讯新闻前端团队 2022 年的 25 个 Awesome 项目推荐;上游 README 固定到 2021-09-05 commit b74188f860080bd94ce31d7575c0a9447fc70598;GitHub 未声明 SPDX 许可且根目录没有 LICENSE 下方保留固定版本入口。
仓库内容地图
- React 17:源码调试、Fiber、Virtual DOM
- React 16:render、调度、组件更新和 diff
- Redux:状态流与订阅模型
- Vue 2、Vue Router、Vuex:响应式与路由/状态实现
- 微前端:从零搭建运行时与隔离机制
- 近期更新:SWR 等数据获取思路
怎么读才有收获
从一个可复现的入口函数开始,用断点追踪输入、调度与提交,而不是一上来通读整个框架。笔记里的版本跨度很大,概念可以迁移,具体内部函数和文件路径不能直接当作当代版本事实。
一、源码阅读先建立版本边界
这个仓库的 README 是阅读目录,不是框架规范:它混合 React 16/17、Vue 2、旧版 Vue Router/Vuex、Redux、微前端和 SWR 等不同年代材料。它在 2021-09-05 更新,仓库没有 SPDX 许可声明或根目录 LICENSE。本文按 README 的主题重新写成现代工程学习路线,不复制仓库中约 15,328 词的第三方源码分析、个人联系方式或二维码;旧文件路径和内部实现只能用于理解历史版本。
每次打开源码先固定 commit/tag、运行时、构建方式、浏览器和复现步骤。把结论分成三栏:语言/平台保证、框架公开 API、当前版本内部实现。只有第一栏和明确文档承诺的第二栏适合变成稳定约束;第三栏要标注版本和证据,升级后重验。
二、React:从更新触发到 DOM 提交
Render 与 Commit 是两种不同工作
React 官方学习资料将界面更新分成 trigger、render、commit:更新状态触发调度;render 阶段调用组件并计算下一份 UI;commit 阶段才把必要改动应用到 DOM。组件函数重新运行不代表整个 DOM 都重建。源码追踪时沿着一次状态更新记录“谁发起、何时排队、组件得到什么输入、提交了哪些变化”,比从 Fiber 文件名猜算法更容易验证。
Render 必须保持纯计算。同一输入应得到同一输出,不在渲染函数里直接修改外部状态、注册永久 listener 或启动不能清理的请求。开发 Strict Mode 可能重复调用渲染相关逻辑来暴露副作用,这不是可以依赖的生产执行次数。
State 是一次渲染的快照
事件回调捕获它创建时那次 render 的变量。连续更新若依赖前一个状态,应使用 updater 或 reducer 表达转移;不要把 setState 当成同步赋值。把“需要共享的状态”提升到最近的共同拥有者,服务端数据、URL 状态和短暂 UI 状态也不要混进一个万能 store。
Fiber 是 React 用于描述工作单元和协调更新的内部实现概念。旧版 Fiber/调度文章能帮助理解为什么渲染工作可被组织和排序,但字段名、优先级常量、Lane/ExpirationTime 细节会随版本变化。做性能分析要测 commit、长任务、重渲染原因和用户交互,不要依赖内部对象结构写业务逻辑。
Diff 与 Key:保留的是身份,不是魔法
列表 key 应代表稳定业务身份,而非数组当前下标。排序、插入和删除后 key 变化会让组件状态跟着错误元素移动或导致整项重挂载。比较同类型/不同类型节点、条件分支与受控表单时,观察 DOM 是否保留、effect 是否清理、输入焦点是否稳定;“虚拟 DOM 一定更快”不是可测量的结论。
三、Vue:从依赖收集读懂响应式边界
Vue 2 主要通过 getter/setter 追踪对象属性;Vue 3 对 reactive 对象使用 Proxy、对 ref 使用访问器。运行时在 effect 执行期间记录读取到的 target/key,再在写入时通知订阅者。源码实验应至少覆盖新增/删除属性、数组、解构、原始值、嵌套对象和代理身份,并和目标主版本的公开文档核对。
将 reactive 对象属性解构成局部原始变量,会失去对原属性访问的追踪;ref 则通过 .value 访问。computed 表达可缓存的派生值,watch/watchEffect 用于有副作用或需要控制时机的工作。请求、timer 和事件订阅在 watcher 失效或组件卸载时应取消/清理;不要用副作用去维护本可直接计算的重复状态。
Vue Router 的导航守卫适合做客户端导航体验与重定向,但不是服务端授权;真正访问 API 时,服务端仍需按每个请求检查身份和资源权限。Vuex 与 Pinia 是状态管理实现,不应和服务端缓存、URL query、表单草稿混为一谈。迁移旧 Vue 2 页面时,先拆分依赖 Vue 2 响应式语义的部分,再验证 Router、store、SSR 和插件生命周期。
四、Redux 与数据派生:让状态转移可验证
Redux 的核心模型是 action 描述事件、reducer 纯函数计算新状态、store 持有状态并通知订阅者;middleware 可在 dispatch 周围处理副作用。不要在 reducer 中请求网络、读当前时间或修改输入对象。异步通常交给应用层 thunk/RTK Query 等方案,而不是声称“Redux 本身就是请求缓存”。
Selector 从规范化状态派生视图数据,能避免重复保存同一事实;若派生计算昂贵或引用稳定性重要,再用 memoization。选取状态粒度过大,会让无关变化造成过多渲染;过度切碎则增加维护成本。测试 reducer 时覆盖不认识的 action、边界输入和对象不可变性,测试 selector 时检查筛选、排序、空数据和缓存失效。
function selectOpenTasks(state) {
return state.tasks.filter(task => task.status !== "done");
}
function tasksReducer(state = [], action) {
switch (action.type) {
case "task/added":
return [...state, action.payload];
case "task/removed":
return state.filter(task => task.id !== action.payload);
default:
return state;
}
}
这里的 reducer 不修改原数组;selector 仍会在每次调用时创建数组。若组件订阅依赖引用相等性,应按真实性能需要选择 memoized selector,而不是无条件增加缓存库。
五、SWR:服务端缓存的生命周期
SWR 的 stale-while-revalidate 思路是先复用缓存数据,再按策略重新验证;focus/reconnect 重取、请求去重和 mutation 属于服务端状态生命周期,不等于客户端领域状态 store。选 key 时包含决定资源身份的参数和租户/账号维度;退出登录或切换账号时清理私有缓存,并取消旧请求,不能让前一账号的数据短暂显示在下一账号页面。
设计时明确 stale 时间、错误重试、分页 key、并发 mutation、乐观更新回滚和 SSR 隔离。缓存 key 不是访问控制:API 仍须校验每个对象权限。若只为首屏读取一次且数据很少,简单请求状态可能比引入额外缓存抽象更易维护。
六、微前端:边界先于框架
先判断为什么要把应用拆成独立部署:团队自治、发布节奏或技术栈隔离是否足以抵消运行时集成成本。然后明确宿主/子应用责任、路由所有权、加载失败降级、公共依赖版本、CSS 隔离、全局变量、鉴权、监控、灰度与回滚。若只是按页面拆代码,路由级 dynamic import 往往更简单。
运行时方案可选择 iframe 的浏览器隔离边界,或共享 JS 环境中的 Web Components/框架集成/Module Federation。iframe 需要处理尺寸、焦点、导航与跨窗口消息;同页运行时需要主动约束 CSS、事件、依赖版本和副作用。postMessage 必须精确校验 origin 和 payload schema,不能把“同一个域名下加载”当成授权证明。
window.addEventListener("message", event => {
if (event.origin !== "https://portal.example.test") return;
if (!event.data || event.data.type !== "navigate") return;
if (typeof event.data.path !== "string") return;
navigateToAllowedPath(event.data.path);
});
导航消息仍要限制到允许的站内路径,避免把任意 URL 交给路由器。卸载子应用时清理监听器、定时器、观察器、样式节点和共享订阅;在多应用连续切换与并行加载的集成测试中确认没有残留。
七、把“读源码”变成一套可复现的实验
- 固定上游 SHA、运行命令、Node/浏览器版本和最小输入。
- 从公开 API 找到入口,在断点或日志中沿一次数据变化跟踪,不先通读整仓。
- 画出事件序列:调用、状态写入、队列/调度、派生、DOM 提交、effect/清理。
- 做一个能推翻当前假设的反例,例如相同 key、异步竞态、组件卸载或账号切换。
- 记录测试结果和观察工具;升级依赖时重新运行,而不是依赖旧截图或函数名。
- 最后写“业务可依赖的结论”与“此版本实现猜测”两栏,避免把内部细节变成技术债。
当前官方参考
- React:Render and Commit
- React:State as a Snapshot
- Vue:Reactivity in Depth
- Redux:Deriving Data with Selectors
- Pinia:Official Documentation
- Vue Router:Official Documentation
- SWR:Getting Started
- webpack:Module Federation
上游文章入口与边界
以下固定入口对应 README 中的源码阅读文章;具体内容仍由原作者维护,本文没有转载其正文。React 16.8.6 系列链接是历史材料,阅读后需对照当前 React 文档。
- React render 系列:热身篇
- React render 系列:render 流程一
- React render 系列:render 流程二
- React render 系列:调度原理
- React 更新流程:调度任务
- React 更新流程:diff 策略
固定源码目录快照:KieSun/awesome-frontend-source-interpretation commit b74188f860080bd94ce31d7575c0a9447fc70598。README 含微前端、React 17、React 16.8.6、Redux、Vue 2、Vue Router、Vuex、SWR 等主题;其中关于作者的联系方式和二维码不属于技术内容,本站不复制。完整源仓库入口保留在文章末尾。
动手路线
- 在仓库指定版本运行最小 React 组件,跟踪一次 setState 到 commit。
- 画出 Vue 2 依赖收集与通知链,再与当前 Vue 版本公开 API 对比。
- 为微前端做一个路由切换和样式隔离实验,记录卸载后的残留资源。
使用边界
源码文章是特定版本的学习材料。生产代码应依赖公开稳定 API,不依赖内部字段或旧版函数名;上游没有声明许可证,本文不转载其长篇分析或作者个人资料。
原始入口
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。