Vue.js 3 的设计思路

🎈声明式地描述UI

Vue.js 3 是一个声明式的UI框架。设计一个这样的框架,我们需要了解编写前端页面都涉及哪些内容?具体如下:

  • DOM元素:是div标签还是a标签。
  • 属性:如a标签的href属性,再如idclass等通用属性
  • 事件:如clickkeydown等。
  • 元素的层级结构:DOM树的层级结构,既有子节点,又有父节点

如何声明式的描述上述内容呢?

  • 使用与 HTML 标签一致的方式来描述 DOM 元素,例如使用 <div></div>来描述一个 div 标签。
  • 使用与 HTML 标签一致的方式来描述属性,例如<div id='app'></div>
  • 使用:v-bind来描述动态绑定的属性,例如<div :id='Tshe'></div>
  • 使用@v-on来描述事件,例如点击事件<div @click="e"></div>
  • 使用与HTML标签一致的方式来描述层级结构,例如一个具有span子节点的div标签 <div><span></span></div>

除了上面这种使用模板来声明式地描述UI之外,还可以使用JavaScript对象来描述,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
const title = {
// 标签名称
tag: 'h1'
// 标签属性
props: {
children: handler
},
// 子节点
children: [
{ tag: 'span' }
]
}

对应到 Vue.js 模板就是:

1
<h1 @click="handler"><span></span></h1>

🎈初识渲染器

我们可以使用JS对象来描述真实的DOM结构。那么虚拟DOM又是如何通过渲染函数转为真实DOM后,渲染到页面中的呢? 渲染器的作用就是把虚拟DOM渲染为真实DOM.

假如我们有如下虚拟DOM:

1
2
3
4
5
6
7
const vNode = {
tag:'div',
props: {
onClick: () => alert('hello')
},
children: 'click me'
}

接下来,我们需要编写一个渲染器,把上面这段虚拟DOM渲染为真实DOM,renderer函数接收两个参数:vnode虚拟DOM对象与container真实DOM挂载点,渲染器会把虚拟DOM渲染到该挂载点下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function renderer(vnode,container){
// 使用 vnode.tag 作为标签名选创建DOM元素
const el = document.createElement(vnode.tag)
// 遍历vnode.props,将属性、事件添加到DOM元素
for(const key in vnode.props){
if(/^on/.test(key)){
// 如果 key 以 on 开头,说明它是事件
el.addEventListener(
key.substr(2).toLowerCase(), //事件名称 onClick --> click
vnode.props[key] //事件处理函数
)
}
}

// 处理 children
if(typeof vnode.children === 'string'){
// 如果children 是字符串,说明它是元素的文本子节点
el.appendChild(document.createTextNode(vnode.children))
}else if(Array.isArray(vnode.children)){
//递归调用 renderer 函数渲染子节点,使用当前元素el作为挂载点
vnode.children.forEach(child => renderer(child,el))
}
//将元素添加到挂载点下
container.appendChild(el)
}

渲染器renderer的实现思路:

  • 创建元素
  • 为元素添加属性和事件
  • 递归遍历children创建节点

🎈组件的本质

组件就是一组DOM元素的封装,这组DOM元素就是组件要渲染的内容,因此可以定义函数来描述组件本身的内容。

1
2
3
4
5
6
7
8
9
const MyComponent = function(){
return {
tag:'div',
props:{
onClick:() => alert('hello')
},
children:'click me'
}
}

这时候的虚拟DOM就是这样的:

1
2
3
const vnode = {
tag: MyComponent
}

🎈模板的工作原理

无论是手写虚拟 DOM还是使用模板,都是属于声明式 UI,上文中讲过,需要将虚拟 DOM 转换为真实 DOM,这一过程需要的就是编译器。 编译器和渲染器一样,就是一个程序,编译器的作用就是将模板编译成渲染函数,如下模板:

1
<div @click="handler">click me</div>

最终通过编译器编译,会将其转化为渲染函数:

1
2
3
4
5
export default {
render() {
return h('div', { onClick: handler }, 'click me')
}
}

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

🎈Vue.js是各个模块组成的有机整体

在Vue.js框架设计中,组件的实现依赖于渲染器和编译器,渲染器和编译器之间是互相关联、互相制约的,它们共同构成一个有机整体,不同模块之间互相配合,进一步提升框架性能。