Vue.js 设计与实现 - 03 Vue.js 3 的设计思路

在第 1 章中,我们阐述了框架设计是权衡的艺术,这里面存在取舍,例如性能和可维护性之间的取舍、运行时和编译时之间的取舍。在第 2 章中,我们详细讨论了框架设计的几个核心要素,有些要素是框架设计者必须要考虑的,另一些要素则是从专业和提升开发体验的角度考虑的。框架的设计将就全局视角的把控,一个项目就算在大,也是存在一条核心思路的,并围绕核心展开。本章就从全局视角了解 Vue.js 3 的设计思路、工作机制及其重要的组成部分。可以把这些组成部分当作独立的功能模块,看看它们之间是如何相互配合的。后续的章节中,我们会深入各个功能模块了解它们的运作机制。

第一篇 框架设计概览 虚拟DOM 渲染器 组件 模板 编译器

3.1 声明式描述 UI(模板、Javascript 对象)

Vue.js 3 是一个声明式的 UI 框架,意思是用户在使用 Vue.js 3 开发页面时是声明式地描述 UI 的。

在 Vue 中,我们可以使用模板来描述 UI,模板中可以使用各种标签来描述 UI,哪怕是事件,也有与之对应的描述方式。用户不需要手写任何命令式代码,这就是所谓的声明式描述 UI。

1
2
3
<div @click="handleClick">
<span></span>
</div>

除了使用模板来描述 UI,我们还可以用 JavaScript 对象来描述:

1
2
3
4
5
6
7
8
9
const test = {
tag: 'div',
props: {
onClick: handleClick
},
children: [
{ tag: 'span' }
]
}

使用 JavaScript 对象描述 UI 更加灵活,但是它的可读性不如模板。而用 JavaScript 对象描述 UI 的方式,就是所谓的虚拟DOM

Vue 会根据组件的 render 函数的返回值,拿到虚拟 DOM,然后根据虚拟 DOM 生成真实 DOM,最后将真实 DOM 挂载到页面中。

3.2 初识渲染器

我们已经了解了什么是虚拟DOM(用JavaScript 对象来描述真实的 DOM 结构)。那么,虚拟DOM 是如何变成真实 DOM 渲染到浏览器页面中的呢 —— 渲染器

实现思路:

  • 把 vnode.tag 作为标签名创建 DOM 元素
  • 为元素添加属性和事件
    • 遍历 vnode.props 对象,key 以 on 开头的是事件,否则是属性
  • 处理 children
    • 如果 children 是一个数组,递归调用 render 函数,将返回的 vnode 作为子节点添加到元素中
    • 如果 children 是一个字符串,创建文本节点添加到元素中

3.3 组件的本质

组件是一组 DOM 元素的封装,这组 DOM 元素就是组件要渲染的内容,因此可以定义一个函数来代表组件,而函数的返回值(也是虚拟 DOM)就代表组件要渲染的内容

3.4 模板的工作原理

模板是如何工作的 —— 编译器

编译器的作用是将模板编译为渲染函数:

1
2
3
4
5
// <div @click="handleClick">click me</div>
// 编译后
render () {
return h('div', { onClick: handleClick }, 'click me')
}

无论是使用模板还是手写渲染函数,对于一个组件来说,要渲染的内容最终都是通过渲染函数产生的,然后渲染器再把渲染函数返回的虚拟 DOM 渲染为真实 DOM,这就是模板的工作原理,也是 Vue.js 渲染页面的流程。

3.5 Vue.js 是各个模板组成的有机整体

如前所述,组件的实现依赖于渲染器,模板的编译依赖于编译器,并且编译后生成的代码是根据渲染器和虚拟 DOM 的设计决定的,因此 Vue.js 的各个模板之间是互相关联、互相制约的,共同构成一个整体。

总结