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

引言

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

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

初期准备

bash 复制代码
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 创建的第一站。

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

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

mount 打断点

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

主流程:props / data / computed / watch

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

data 绑定:把 data 暴露到 ctx

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

js 复制代码
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:依赖收集与访问触发

依赖收集断点

访问触发断点

在模板里出现:

html 复制代码
<div v-html="compiledMarkdown"></div>

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

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

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

js 复制代码
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
}

生命周期注册

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

js 复制代码
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. 明确数据流与调用链

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

相关推荐
SuperEugene1 小时前
数组查找与判断:find / some / every / includes 的正确用法
前端·javascript
~央千澈~1 小时前
抖音弹幕游戏开发之第11集:礼物触发功能·优雅草云桧·卓伊凡
java·前端·python
top_designer2 小时前
Magnific:老旧 UI 糊成马?720p 截图重铸 4K 界面
前端·游戏·ui·prompt·aigc·设计师·游戏策划
Cache技术分享2 小时前
326. Java Stream API - 实现自定义的 toList() 与 toSet() 收集器
前端·后端
PythonFun2 小时前
WPS动态序号填充,告别手动调整烦恼
java·前端·python
Cache技术分享2 小时前
325. Java Stream API - 理解 Collector 的三大特性:助力流处理优化
前端·后端
Wcowin2 小时前
【2】 Zensical配置详解
前端·github
用户1085932993412 小时前
Options API 与 Composition API 对照表
vue.js
REDcker2 小时前
Web 音视频流媒体 API 全景
前端·音视频