obsidian-computer
前端识记mini-vue-reading

00-总览与阅读路线

第 0 章 · 总览与阅读路线

最后更新 2026年8月29日

第 0 章 · 总览与阅读路线

在打开任何一个源码文件之前,必须先回答三个问题:这个项目由哪几块组成?块与块之间谁依赖谁?我该从哪一块进去? 这一章就干这一件事。


0.1 mini-vue 的包结构

mini-vue 复刻了 Vue 3 的 monorepo 结构(Vue 3 在 packages/ 下有 20+ 个包,mini-vue 保留了最核心的 5 个):

mini-vue/src
├── shared/          # 公共工具:isObject、hasChanged、ShapeFlags、camelize...
├── reactivity/      # 响应式系统:reactive、ref、effect、computed
├── runtime-core/    # 平台无关的运行时:vnode、组件、patch、diff
├── runtime-dom/     # 浏览器平台实现:真正操作 DOM 的 5 个函数
└── compiler-core/   # 编译器:template 字符串 → render 函数源码

每个包各自有 index.ts 做统一导出,有自己的 __tests__/ 目录。测试目录是本剖析反复引用的对象——它是每段代码的"存在理由清单"。

0.2 三大系统怎么协作:跑一遍完整链路

拿一个最小的 Vue 应用,把三大系统串起来走一遍。这个心智模型贯穿全部章节:

<div>{{ message }}</div>
createApp({
  setup() {
    const message = ref('hello')
    return { message }
  }
}).mount('#app')

发生的事情按时间顺序:

【编译时 · compiler】
  "<div>{{ message }}</div>"
    → parse    → AST(树形对象,描述"一个 div,里面是插值 message")
    → transform → 加工 AST(标记哪些是动态的、准备好生成代码需要的信息)
    → codegen  → "function render(_ctx) { return h('div', null, _ctx.message) }"

【运行时首次挂载 · runtime + reactivity】
  mount('#app')
    → 创建组件实例 instance
    → 执行 setup(),拿到 { message }(ref 是 reactivity 提供的)
    → 把 render 函数包进 effect 里执行          ← ★ 两大系统在这里接线
        → render 执行,读了 message.value
        → reactivity 记下:"这个组件的渲染依赖 message"(track)
        → render 返回 vnode 树
    → patch(null, vnode):递归创建真实 DOM,插入 #app

【数据变化 · reactivity 驱动 runtime】
  message.value = 'world'
    → reactivity 发现 message 被写(trigger)
    → 查表:"谁依赖 message?"→ 那个组件的渲染 effect
    → 重新执行 effect → render 再跑一遍 → 新 vnode 树
    → patch(旧vnode, 新vnode):对比两棵树,只更新变了的文本

一句话总结三者分工:

compiler 负责把模板翻译成 render 函数(只做一次); runtime 负责执行 render 函数、把 vnode 变成真实 DOM、以及新旧 vnode 的对比更新; reactivity 负责回答"数据变了,谁需要重新执行"。

0.3 依赖方向决定阅读顺序

画出包依赖图(箭头 = 依赖):

runtime-dom ──→ runtime-core ──→ reactivity ──→ shared
                     ↑
compiler-core ───────┘(产物 render 函数调用 runtime 的 h/createElementVNode)

三个观察:

  1. reactivity 除了 shared 之外零依赖。它不知道组件、不知道 vnode、不知道 DOM 的存在,是一个可以单独发包使用的库(Vue 官方确实单独发布了 @vue/reactivity)。零依赖 = 不需要任何前置知识就能读 = 最佳入口。
  2. runtime-core 不依赖 runtime-dom,反而是被依赖。这是故意倒转的:核心运行时通过"参数注入"拿到平台操作函数,而不是直接 import DOM API。第 3 章细讲。
  3. compiler 和 runtime 几乎不相互 import,它们通过一个"约定的产物格式"(render 函数长什么样)解耦。这就是为什么 Vue 可以只用 runtime(手写 render 函数 / 构建时预编译),把 compiler 从生产包里整个剔除。

所以阅读顺序是确定的,不是品味问题:

reactivity(1-2 章) → runtime 挂载(3-4 章) → runtime 更新(5 章) → compiler(6 章)

0.4 每一章的读法约定

后面每章遵循同一个结构,这个结构本身就是"源码是怎么写出来的"的还原:

  1. 问题:这个文件/函数要解决什么,不解决会怎样;
  2. 最小失败用例:mini-vue 的作者是 TDD 式开发的,每个特性先有一个红的测试。我们把这个测试摆出来——它是这段代码唯一的需求文档;
  3. 代码:完整实现,不省略;
  4. 逐段解释:按代码块顺序讲每段为什么这么写、换种写法会死在哪个用例上;
  5. 陷阱与细节:容易读漏、面试常问、或真源码里处理得更复杂的点。

0.5 开始前的准备(10 分钟)

git clone https://github.com/cuixiaorui/mini-vue.git
cd mini-vue
pnpm install
pnpm test          # 全部测试跑通,绿色,心里有底

再执行一条命令,它的输出就是官方阅读顺序:

git log --oneline --reverse

mini-vue 是按"一个功能一个 commit"开发的,最早的 commit 就是 feat: effect & reactive——和本剖析的章节顺序一致。读某段代码困惑时,git log -p -- src/reactivity/effect.ts 看它的演化史,比盯着最终形态猜要快得多。


自测

进入第 1 章前,不看上文回答:

  1. 三大系统各自的一句话职责?
  2. 为什么 reactivity 是最佳阅读入口?(两个理由)
  3. runtime-core 为什么不 import runtime-dom,反而被它依赖?

反向链接(1)

评论

关系图谱

00-总览与阅读路线01-reactivity-effect与依赖收集02-reactivity-reactive-ref-computed03-runtime-挂载流程04-runtime-组件系统05-runtime-更新与diff06-compiler-三段式07-方法论-如何啃大型开源项目README

On this page