虚拟DOM本质上就是一个JavaScript对象,这个对象是对真实DOM结构的描述。
举个例子:
// 虚拟DOM,对象形式
const obj = {
tag: 'div',
children: 'Hello world'
}
// h函数写法(h函数用于创建虚拟DOM节点即vnode)
h('div', { innerHTML: 'hello'})
通过渲染器(renderer)可以将这个对象渲染成真实DOM
// 对应的真实DOM
<div>Hello World</div>
一个更复杂一点的例子:
// 虚拟DOM,对象写法
const title = {
// 标签名称
tag: 'h1',
// 标签属性
props: {
onClick: handler
},
// 子节点
children: [
{ tag: 'span' }
]
}
// h函数写法
h('h1', {onClick: handler})
// 对应的真实DOM
<h1 @click="handler"><span></span></h1>
那么渲染器(renderer)是如何实现把虚拟DOM渲染成真实DOM的呢?
假如让你来实现,可能你会思考出如下的步骤:
-
接收虚拟 DOM 对象和想要挂载的根节点(真实DOM)
-
将它的 tag 作为标签名来创建 DOM 元素
-
为元素添加属性和事件
-
处理子节点(children)
-
将生成的 DOM 元素挂上根节点
非常好的思路!!但是听起来可能有点抽象,那么来看看具体的实现过程:
// 首先,这是你准备好的虚拟DOM
const title = {
// 标签名称
tag: 'h1',
// 标签属性
props: { onClick: handler },
// 子节点
children: [{ tag: 'span', 'childNode' }]
}
// 开始设计渲染器
function renderer(vnode, container) {
// 1.使用 tag 作为标签名
const el = document.createElement(vode.tag)
// 2.遍历 props 为元素添加属性和事件
for(const key in vnode.props) {
// 如果 key 以 on 开头,说明是事件
if(/^on/.test(key)) {
// key.substr(2).toLowerCase()是将事件名 onClick 转换成 click
el.addEventListener(key.substr(2).toLowerCase(), vnode.props[key])
}
}
// 3.处理 children
if(typeof vnode.children === 'string') {
// 如果 children 是字符串,如例一,说明它是文本子节点
el.appendChild(document.createTextNode(vnode.children))
} else if {
// 如果是一个数组,递归调用renderer,将子元素挂载上父元素
vnode.children.forEach(child => renderer(child, el))
}
// 4.将创建好的真实DOM挂载上根节点,你就可以在页面上看到它们了
container.appendChild(el)
}
看到这里,相信你已经大致理解了渲染器将虚拟DOM渲染成真实DOM的过程,其实并没有想象的那么复杂。
最后再重复一遍,虚拟DOM本质上就是一个JavaScript对象,这个对象是对真实DOM结构的描述。