前言
虚拟DOM就是用一个原生的js对象来描述DOM
,那么vnode是如何渲染成真实的dom,本文通过手写简单案列简要的分析其原理,主要关注以下几点:
Vue
中如何描述UI
vnode
是如何生成的vnode
是如何渲染成真实的dom
Vue
中如何描述UI
我们编写前端页面都涉及哪些内容,具体如下:
- DOM元素:例如是div标签还是a标签。
- 属性:如a标签的href属性,再如id、class等通用属性。
- 事件:如click、keydown等。
- 元素的层级结构,DOM树的层级结构,既有子节点,又有父节点。
使用Vue开发时页面是声明式地描述UI,那么,如何声明式的描述上述内容呢?在Vue.js中,哪怕是事件,都有与之对应的描述方式。具体如下:
- 使用与HTML标签一致的方式来描述DOM元素,例如
<div></div>
;- 使用与HTML标签一致的方式来描述属性,例如
<div id="app"></div>
;- 使用:或v-bind来描述动态绑定的属性,例如
<div :id="myApp"></div>
;- 使用@或v-on来描述事件,例如点击事件
<div @click="handler"></div>
;- 使用与HTML标签一致的方式来描述层级结构,例如一个具有span子节点的div标签
<div><span></span></div>
vnode
如何生成?
以我们熟悉的.vue文件为例,一个vue文件就是一个组件,如下所示:
js
<template>
<div @click="handler">
点击按钮
</div>
</template>
<script>
export default {
data(){
return {}
},
methods:{
handler(){
alert('hello')
},
}
}
</script>
编译器会把模板内容编译成渲染函数并添加<script>
标签块的组件对象上,所以最终在浏览器里运行的代码就是:
js
export default {
data(){
return {}
},
methods:{
handler(){
alert('hello')
},
},
render(){
return h('div',{onClick:handler},'点击按钮')
}
}
一个组件要渲染内容是通过渲染函数来描述的,也就是上述代码中的render
函数,Vue.js会根据组件的render函数的返回值拿到虚拟DOM。其中上述涉及到的h函数,其实就是一个辅助创建虚拟DOM的工具函数,仅此而已。
vnode
是如何渲染成真实的dom
那么虚拟DOM是如何变成真实DOM渲染到浏览器页面中呢?这就用到了我们接下来要介绍的:渲染器
上述渲染函数返回如下虚拟DOM:
js
const vnode = {
tag:'div',
props:{
onClick:handler
},
children:'点击按钮'
}
接下来,我们编写一个简单的渲染器,把上面这段虚拟DOM渲染为真实DOM:
js
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)){
el.addEventListener(
key.substr(2).toLowerCase(), // 事件名称 onClick-->click
vnode.props[key] // 事件处理函数
)
}
}
// 处理children
if(typeof vnode.children === 'string'){
// 如果是字符串,说明它是元素的文本节点
el.appendChild(document.createTextNode(vnode.children))
}else if(vnode.children){
obj.children.forEach((child)=>renderer(child,el))
}
// 将元素添加到挂载点下
container.appendChild(el)
}
这里的 renderer 函数接收如下两个参数。
- vnode:虚拟 DOM 对象。
- container:一个真实 DOM 元素,作为挂载点,渲染器会把虚拟 DOM 渲染到该挂载点下。
渲染器其原理是:递归地遍历虚拟DOM对象,并调用原生DOM API来完成真实DOM的创建。渲染器的精髓在于后续的更新,它会通过diff算法找出变更点,并且只会更新需要更新的内容。当然,我们此处只是简单实现其功能。
接下来,我们可以调用 renderer 函数:
js
renderer(vnode, document.body) // body 作为挂载点
在浏览器中运行这段代码,会渲染出"点击按钮"文本,点击该文本,会弹出 alert('hello'),如下图所示。
总结
如前所述,虚拟DOM转化成真实DOM的过程,离不开编译器和渲染器这两个非常关键的模块相互配合,并实现性能提升的。可见 Vue.js 的各个模块之间是互相关联、互相制约的,共同构成一个有机整体。因此,我们在学习Vue.js 原理的时候,应该把各个模块结合到一起去看,才能明白到底是怎么回事。
[注]:水平有限,文中如果有错误或表达不当的地方,非常欢迎在评论区指出,感谢~