引言
在第1章中,我们阐述了框架设计是权衡的艺术,这里面存在取舍,例如性能与可维护性之间的取舍、运行时与编译时之间的取舍等。 在第2章中,我们详细讨论了框架设计的几个核心要素,有些要素是框架设计者必须要考虑的,另一些要素则是从专业和提升开发体验的角度考虑的。 框架设计讲究全局视角的把控,一个项目就算再大,也是存在一条核心思路的,并围绕核心展开。 本章我们就从全局视角了解 Vue.js 3 的设计思路、工作机制及其重要的组成部分。我们可以把这些组成部分当作独立的功能模块, 看看它们之间是如何相互配合的。在后续的章节中,我们会深入各个功能模块了解它们的运作机制。
Vue.js 3 是一个声明式的 UI 框架,意思是说开发者在使用 Vue.js 3 开发页面时是声明式地描述 UI 的。 思考一下,如果让你设计一个声明式的 UI 框架,你会怎么设计呢?为了搞清楚这个问题,我们需要了解编写前端页面都涉及哪些内容,具体如下:
- DOM 元素:例如是 div 标签还是 a 标签。
- 属性:如 a 标签的 href 属性,再如 id、class 等通用属性。
- 事件:如 click、keydown 等。
- 元素的层级结构:DOM 树的层级结构,既有子节点,又有父节点。
那么,如何声明式地描述上述内容呢?这是框架设计者需要思考的问题。其实方案有很多。拿 Vue.js 3 来说,相应的解决方案是:
- 使用与 HTML 标签一致的方式来描述 DOM 元素,例如描述一个div 标签时可以使用
<div></div>
。 - 使用与 HTML 标签一致的方式来描述属性,例如
<div id="app"></div>
。 - 使用 : 或 v-bind 来描述动态绑定的属性,例如
<div :id="dynamicId"></div>
。 - 使用 @ 或 v-on 来描述事件,例如点击事件
<div @click="handler"></div>
。 - 使用与 HTML 标签一致的方式来描述层级结构,例如一个具有 span 子节点的 div 标签
<div><span></span></div>
。
可以看到,在 Vue.js 中,哪怕是事件,都有与之对应的描述方式。用户不需要手写任何命令式代码,这就是所谓的声明式地描述 UI。
除了上面这种使用模板来声明式地描述 UI 之外,我们还可以用 JS 对象来描述,代码如下所示:
const title = {
// 标签名称
tag: 'h1',
// 标签属性
props: {
onClick: handler
},
// 子节点
children: [
{ tag: 'span' }
]
}
对应到 Vue.js 模板,其实就是:
<h1 @click="handler"><span></span></h1>
那么,使用模板和 JS 对象描述 UI 有何不同呢?答案是:使用 JS 对象描述 UI 更加灵活。举个例子,假如我们要表示一个标题, 根据标题级别的不同,会分别采用 h1~h6 这几个标签,如果用 JS 对象来描述,我们只需要使用一个变量来代表 h 标签即可:
// h 标签的级别
let level = 3
const title = {
tag: `h${level}`, // h3 标签
}
可以看到,当变量 level 值改变,对应的标签名字也会在 h1 和 h6 之间变化。但是如果使用模板来描述,就不得不穷举:
<h1 v-if="level === 1"></h1>
<h2 v-else-if="level === 2"></h2>
<h3 v-else-if="level === 3"></h3>
<h4 v-else-if="level === 4"></h4>
<h5 v-else-if="level === 5"></h5>
<h6 v-else-if="level === 6"></h6>
这远没有 JS 对象灵活。而使用 JS 对象来描述 UI 的方式,其实就是所谓的虚拟 DOM。现在大家应该觉得虚拟 DOM 其实也没有那么神秘了吧。 正是因为虚拟 DOM 的这种灵活性,Vue.js 3 除了支持使用模板描述 UI 外,还支持使用虚拟 DOM 描述 UI。 其实我们在 Vue.js 组件中手写的渲染函数就是使用虚拟 DOM 来描述 UI 的,如以下代码所示:
import { h } from 'vue'
export default {
render() {
return h('h1', { onClick: handler }) // 虚拟 DOM
}
}
有的读者可能会说,这里是 h 函数调用呀,也不是 JS 对象啊。其实 h 函数的返回值就是一个对象,其作用是让我们编写虚拟 DOM 变得更加轻松。 如果把上面 h 函数调用的代码改成 JS 对象,就需要写更多内容:
export default {
render() {
return {
tag: 'h1',
props: { onClick: handler }
}
}
}
如果还有子节点,那么需要编写的内容就更多了,所以 h 函数就是一个辅助创建虚拟 DOM 的工具函数,仅此而已。
🔔 提示
在 Vue 中,createElement 函数(简称 h 函数)是用于辅助创建虚拟 DOM 对象的工具函数。它接收一些参数(如标签名、属性、子节点等), 并返回一个表示 DOM 结构的虚拟 DOM 对象。通过使用 h 函数,开发者可以以编程的方式构建虚拟 DOM 树,而不是直接操作真实 DOM。
“h 函数就是一个辅助创建虚拟 DOM 的工具函数”这句话的意思是,h 函数在 Vue 中起到了一个辅助作用,帮助开发者更方便地创建和组织虚拟 DOM 结构。 当 Vue 需要渲染或更新组件时,它会首先使用 h 函数创建虚拟 DOM,然后将其转换为真实 DOM,并在必要时进行高效的更新。 简单地说,h 函数就是一个用于创建虚拟 DOM 的辅助工具,在 Vue 中起到了连接虚拟 DOM 和真实 DOM 的关键作用。
另外,这里有必要解释一下什么是组件的渲染函数。一个组件要渲染的内容是通过渲染函数来描述的,也就是上面代码中的 render 函数, Vue.js 会根据组件的 render 函数的返回值拿到虚拟 DOM,然后就可以把组件的内容渲染出来了。