产出:Vue3源码库 MVP 版~ vue-mini
Vue3核心模块
- reactivity:响应性
- runtime:运行时
- compiler:编译器
vue3源码GitHub:github.com/vuejs/core
www.yuque.com/g/daiyubuzh… 《Vue3源码》
1. 编程范式:声明式、命令式
区别:
- 声明式关注“如何做”,需手动控制每一步操作流程(关注过程 - 详细逻辑与步骤)。
- 命令式关注“做什么”,描述目标状态而非具体操作(关注结果 - 过程隐藏)。
| 维度 | 命令式 | 声明式 |
|---|---|---|
| 关注点 | 如何实现(步骤) | 目标状态(结果) |
| 代码量 | 较多(需手动操作) | 较少(框架自动化) |
| 维护性(对代码方便的:阅读、修改、删除、增加) | 低(易产生冗余代码) | 高(逻辑与视图解耦) |
| 性能(耗时越少,性能越强) | 1 | 1+n |
| 命令式 > 声明式 | ||
| 典型框架 | jQuery、原生JS | Vue、React、ArkUI |
示例:
- 命令式编程
// 命令式:手动创建元素、设置属性、绑定事件
const button = document.createElement('button');
button.textContent = '点击我';
button.style.color = 'blue';
button.addEventListener('click', () => {
button.textContent = '已点击';
button.style.color = 'red';
});
document.body.appendChild(button);
- 开发者需明确写出创建元素、修改样式、绑定事件等步骤。
- 代码冗长且与DOM强耦合,维护成本高
- 声明式编程(Vue示例)
<!-- 声明式:通过模板描述UI状态 -->
<template>
<button
:style="{ color: textColor }"
@click="handleClick">
{{ buttonText }}
</button>
</template>
<script>
export default {
data() {
return {
buttonText: '点击我',
textColor: 'blue'
};
},
methods: {
handleClick() {
this.buttonText = '已点击';
this.textColor = 'red';
}
}
};
</script>
- 开发者仅需声明按钮的文本、样式及交互逻辑,无需操作DOM。
- Vue内部自动处理状态到视图的映射,代码更简洁
- 混合使用示例(Vue)
// Vue中命令式访问DOM
const inputRef = ref(null);
onMounted(() => {
inputRef.value.focus(); // 手动聚焦输入框
});
2. 框架实现概念
2.1. 企业应用的开发与设计原则:项目成本+开发体验
- 项目成本 =》 开发周期 =》可维护性
- 开发体验 =》心智负担更低
企业应用开发需平衡项目成本(预算、资源、时间)与开发体验(效率、可维护性、团队协作),核心原则包括:
- 模块化设计:通过组件化降低重复开发成本,提升代码复用率(如微服务架构);
- 技术选型适配性:选择成熟框架(如Spring Boot、Vue)减少学习成本,避免过度追求新技术;
- 自动化工具链:CI/CD流水线(如Jenkins)和低代码平台可压缩人力成本,加速交付;
- 开发者体验优化:完善的文档、调试工具和标准化规范能减少沟通损耗。
典型案例:
- 成本控制:采用云服务(AWS/Aliyun)按需付费,避免基础设施过度投入;
- 体验提升:使用React+TypeScript增强代码可读性,降低维护难度。
最终目标是通过技术决策与流程设计,实现成本可控与开发高效的双赢。
2.2. 框架设计的过程:不断取舍
尤大大在Vue框架设计中的取舍实践:找到可维护性和性能之间的一个平衡点,封装了命令式的逻辑,对外则暴露声明式的接口。
2.3. 编译时、运行时
在Vue框架中,编译时与运行时是两个核心阶段,它们共同协作完成从模板到最终DOM渲染的全过程。以下通过Vue模板的处理流程具体说明:
- 编译时(Compile Time):
当开发者编写Vue单文件组件(Single File Component,SFC)时,模板部分会被编译器处理:
<template>
<div>{{ message }}</div>
</template>
-
编译器作用:将类HTML的模板语法转换为 JavaScript 渲染函数。例如上述模板会被编译为类似
render(h) { return h('div', this.message) }的代码。 -
关键点:
- 编译时在构建阶段(如
vite build或webpack打包)完成,生成浏览器可执行的JS代码。 - 若使用仅运行时版本的Vue(如
vue.runtime.js),则需预编译模板,否则会报错。
- 编译时在构建阶段(如
Vue编译时核心代码:编译器 compiler-core
可以用乐高说明书的比喻来理解Vue3编译时将HTML节点转为render函数的过程:
① 原始乐高图纸(HTML模板)
就像小朋友画的乐高搭建草图:
<div class="house">
<p>{{ message }}</p>
</div>
这种带{{}}的模板浏览器无法直接执行。
② 翻译成步骤说明书(生成AST)
编译器像乐高设计师,把图纸拆解成树形结构说明书(AST):
{
type: "div",
props: { class: "house" },
children: [{
type: "p",
expression: "message" // 动态内容标记
}]
}
这个结构记录了所有零件关系和组装要点。
③ 优化说明书(静态标记)
设计师用黄色荧光笔标出永不改动的部分(如class="house"),后续拼装时可直接跳过检查。
④ 生成拼装指南(render函数)
最终输出机器可执行的组装步骤:
function render() {
return h('div', { class: 'house' }, [
h('p', this.message)
])
}
就像乐高说明书用编号和箭头表示拼装顺序。
完整比喻流程:
原始图纸 → 设计师拆解 → 标记重点 → 生成步骤指南
对应Vue3的:
模板 → AST → 优化 → render函数
这种转换让浏览器能像拼乐高一样,按照明确步骤动态构建DOM
总结:Vue编译时可以把 html 的节点,编译成 render 函数。
- 运行时(Runtime)
运行时阶段发生在浏览器执行编译后的代码时:
- 渲染函数执行: 编译生成的渲染函数被调用,生成虚拟DOM(VNode)树。
- 响应式更新: 组件实例化后,数据变化触发渲染函数重新执行,通过 Diff 算法更新真实DOM。
- 轻量化优势: 运行时版本(不含编译器)体积更小,适合生产环境。
<!-- 开发阶段:SFC文件 -->
<template>
<button @click="count++">{{ count }}</button>
</template>
<!-- 编译后:生成渲染函数 -->
<script>
export default {
render() {
return h('button', { onClick: () => this.count++ }, this.count);
}
}
</script>
<!-- 运行时:浏览器执行渲染函数并绑定事件 -->
Vue运行时核心代码:渲染函数 render
core/packages/runtime-core at main · vuejs/core
可以用搭积木的比喻来理解Vue3运行时将vnode转为真实DOM的过程:
① 设计图纸(vnode)
就像小朋友画积木搭建草图({ type: 'div', children: 'Hello' }),vnode是用JS对象描述DOM该长什么样。
② 挑选积木块(createElement)
render函数像小朋友的手,根据图纸找到对应积木(调用document.createElement创建div)。
③ 组装积木(挂载DOM)
把文字积木"Hello"塞进div积木(el.textContent = vnode.children),最后放到展示架(container.appendChild(el))上。
// 设计图纸(vnode)
const toyHouse = {
type: 'div', // 要搭个方盒子
props: { class: 'house' }, // 涂成红色
children: '我的小房子' // 盒子上贴的字
}
// 搭积木过程(render实现)
function buildToy(vnode, container) {
const block = document.createElement(vnode.type) // 1.找积木
block.className = vnode.props.class // 2.涂颜色
block.textContent = vnode.children // 3.贴文字
container.appendChild(block) // 4.放展示架
}
buildToy(toyHouse, document.body)
就像积木最终变成实物玩具,Vue运行时通过类似步骤把JS对象变成页面真实元素
总结:Vue运行时可以利用 render 把 vnode 渲染成真实的 dom 节点。
2.4. 编译时+运行时
Vue 先通过 compiler 解析 html 模板,生成 render 函数,然后通过 runtime 解析render,从而挂着真实 dom。
问:那么 Vue 为什么要采用 编译时+运行时 的方式来设计呢?
- 纯运行时:因不存在编译器,所以只能提供一个复杂的 JS 对象。
- 纯编译时:因缺少运行时,所以只能把分析差异的操作,放到编译时执行。同样为了省略运行时,所以速度可能会更快。但这种方式损失灵活性,比如 svelte(纯编译时框架),其实际运行速度可能达不到理论上的速度。
- 运行时 + 编译时:比如 Vue、React。保持灵活性的基础上,尽量的进行性能的优化,从而达到一种平衡。
Dom 渲染过程:
① 初次渲染(挂载)
② 更新渲染(打补丁)
2.5. Vue 编译时+运行时混合设计核心原因:
- 性能与灵活性的平衡
- 编译时优化:通过将HTML模板预先编译为渲染函数,可以标记静态节点、优化指令处理,减少运行时计算量。例如v-if会被编译为条件判断语句而非运行时解析。
- 运行时动态性:保留运行时能力允许动态生成模板(如通过API返回的HTML字符串),这是纯编译时框架(如Svelte)无法实现的。
- 开发体验提升
- 声明式模板:开发者可直接编写类HTML的模板(编译时特性),而无需手动编写命令式渲染函数(如React的JSX)。编译器将其转换为高效的JavaScript代码。
- 渐进式适配:支持直接使用未编译的模板(完整版)或预编译模板(运行时版),适应不同项目需求。
- 架构分层优势
-
编译时职责:
- 解析模板为AST并优化(如静态节点标记);
- 处理指令(如v-for转换为循环逻辑)。
-
运行时职责:
- 执行渲染函数生成虚拟DOM;
- 响应式数据更新与补丁(Patch)。
- 体积与效率权衡
- 生产环境优化:通过构建时预编译(如vue-loader),可移除编译器代码,减少30%+体积。
- 开发环境便利:保留运行时编译支持快速迭代。
对比其他方案:
纯运行时(如早期React):需手动优化,心智负担高。
纯编译时(如Svelte):灵活性受限,无法处理动态模板。
这种混合设计使Vue在性能、开发体验和适应性上达到最佳平衡。
3. 副作用
副作用指的是:当我们 对数据进行 setter 或 getter 操作时,所产生的一系列后果。
3.1. setter
setter 表示的是赋值操作
msg = 'hi'
假如 msg 是一个响应性数据,那么这样的一次数据改变,就会影响到对应的视图改变。
那么就可以说,msg 的 setter 行为,触发了一次副作用,导致视图跟随发生了变化。
3.2. getter
getter 表示的是取值操作
element.innerText = msg
对于变量 msg 而言,触发了一次 getter 操作,这样的一次取值操作,同样会导致 element 的 innerText 发生改变。
所以就可以说,msg 的 getter 行为触发了一次副作用,导致 element 的 innerText 发生了变化。
3.3. 思考:副作用会有多个吗
会。
举例:
<template>
<div>
<p>姓名:{{ obj.name }}</p>
<p>年龄:{{ obj.age }}</p>
</div>
</template>
<script>
const obj = ref({
name:'demo',
age:10
})
obj.value = {
name:'demo1',
age:11
}
</script>
obj.value 触发了一次 setter 行为,但是会导致两个 p 标签内容发生改变,也就是产生了两次副作用。
4. 良好的 TS 支持是如何提供的?
错误认识:vue3 对 ts 支持比较好,因为 vue3 本身是使用 ts 编写的。
正确认识:ts 编写的程序 和 ts 类型支持友好是两回事。想要让我们的程序拥有更好地 ts 支持,需要做很多额外的事情。比如 vue 内部其实也做了非常多的事情:packages\runtime-core\src\componentPublicInstance.ts