阅读视图

发现新文章,点击刷新页面。

Vue3 源码解析系列 1:从 Debugger 视角读 Vue

引言

直接把源码当黑盒,或者干巴巴从头读到尾,几乎读不下去。更高效的方式是把源码当作“正在运行的程序”,用断点一层层摸清主流程。

这一篇记录我用经典 markdown.html 示例,跟踪 createApp -> mount -> render 的阅读路径。

初期准备

git clone https://github.com/vuejs/core.git
cd core
pnpm install
pnpm run dev # 生成 dev 版本 Vue

# 用浏览器打开示例
open packages/vue/examples/classic/markdown.html

入口断点:createApp

createApp 开始是最稳定的入口,它是 app 创建的第一站。

createApp 断点

渲染对比:从“看见结果”到“找到入口”

先把渲染结果和源码入口对齐,这样断点才更有目标感。

渲染对比

mount 打断点

mount 是渲染真正开始的地方,后面会进入 renderpatch

mount 断点

主流程:props / data / computed / watch

这个阶段会完成 Options API 的初始化,包括 data 绑定、computed 计算、watch 监听等。

主流程

data 绑定:把 data 暴露到 ctx

data() 返回对象后,会被转成响应式,并在 dev 模式下挂到 ctx 以便访问。

data 绑定

for (const key in data) {
checkDuplicateProperties!(OptionTypes.DATA, key)
// expose data on ctx during dev
if (!isReservedPrefix(key[0])) {
Object.defineProperty(ctx, key, {
configurable: true,
enumerable: true,
get: () => data[key],
set: NOOP,
})
}
}

computed:依赖收集与访问触发

依赖收集断点

computed 依赖收集

访问触发断点

computed 访问触发

在模板里出现:

<div v-html="compiledMarkdown"></div>

就会在渲染时读取 compiledMarkdown,触发 computed 的 getter。完整流程可以拆成:

  1. 读取 computedOptions
  2. 为每个 computed 添加 getter / setter
  3. 模板渲染时访问 compiledMarkdown
  4. 触发 getter,开始依赖追踪

一句话总结:把一个 getter(或 get/set)包装成带缓存的响应式 ref,并在依赖变化时标记为脏。

export function computed(getterOrOptions, debugOptions, isSSR = false) {
// 1. 解析 getter / setter
let getter, setter
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}

// 2. 创建 ComputedRefImpl 实例
const cRef = new ComputedRefImpl(getter, setter, isSSR)

// 3. dev 环境下注入调试钩子
if (__DEV__ && debugOptions && !isSSR) {
cRef.onTrack = debugOptions.onTrack
cRef.onTrigger = debugOptions.onTrigger
}

// 4. 返回一个 ref(带 value getter/setter)
return cRef
}

生命周期注册

生命周期函数在这里统一注册,方便后续统一触发。

lifecycle 注册

registerLifecycleHook(onBeforeMount, beforeMount)
registerLifecycleHook(onMounted, mounted)
registerLifecycleHook(onBeforeUpdate, beforeUpdate)
registerLifecycleHook(onUpdated, updated)
registerLifecycleHook(onActivated, activated)
registerLifecycleHook(onDeactivated, deactivated)
registerLifecycleHook(onErrorCaptured, errorCaptured)
registerLifecycleHook(onRenderTracked, renderTracked)
registerLifecycleHook(onRenderTriggered, renderTriggered)
registerLifecycleHook(onBeforeUnmount, beforeUnmount)
registerLifecycleHook(onUnmounted, unmounted)
registerLifecycleHook(onServerPrefetch, serverPrefetch)

小结

这一篇先把路径跑通:createApp -> mount -> render -> patch -> Options 初始化。后面再深入 patch 和响应式系统时,你会发现思路完全一致:

  1. 找入口
  2. 断点追踪
  3. 明确数据流与调用链

下一篇我会继续浏览更新渲染源码。

❌