开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
关于虚拟DOM的原理、与真实DOM的关系、以及源码的实现思路和分析等,可参考以之前的几篇文章:
真实 DOM 和 虚拟 DOM 的区别,在于是否直接操作 DOM ,如下图:
真实DOM:
使用虚拟DOM:
虚拟 DOM 树由无数个虚拟节点组成。在实现的过程中,我们会用数据(js对象)来存储虚拟 DOM ,这个 js 对象包含以下三个属性:
tag: 标签,比如 divprops/attrs:属性,比如 id、class 等children:子节点
我们平时熟悉的 HTML:
转换成虚拟的之后,就是 JS 对象所表示的 DOM 树:
代码实现
实现思路:
- 定义一个表示虚拟 DOM 的 js 对象,作为转化的数据
- 判断类型。如果是数字类型,转化为字符串,再生成文本节点;如果是字符串类型直接就是文本节点(我们所需要的是字符串类型)
- document.createElement() 用于创建 DOM 节点
- 如果存在属性,则遍历所有的属性,在刚才生成的 DOM 上添加属性及其值(dom.setAttribute(key, value))
- 遍历当前 DOM 节点的子节点,添加子节点(dom.appendChild()),参数通过递归传入(第2条中提到的字符串类型,也是递归的终止条件)
- 最终可以获得一个有父子结构的 DOM,输出或渲染即可
const vnode = {
tag: 'div',
attrs: {
id: 'app'
},
children: [{
tag: 'h1',
attrs: {
className: 'title'
},
children: [
'俺是一个标题'
]
},{
tag: 'ul',
children: [{
tag: 'li',
children: ['苹果','香蕉']
},{
tag: 'li',
children: ['土豆','白菜']
}]
}]
}
const render = (vnode) => {
if(typeof vnode === 'number') {
vnode = String(vnode)
}
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}
// DOM
const dom = document.createElement(vnode.tag);
// 遍历属性
if (vnode.attrs) {
Object.keys(vnode.attrs).forEach( childNode => {
dom.setAttribute(childNode, vnode.attrs[childNode])
})
}
// 递归子节点
if(vnode.children) {
console.log(vnode.children);
vnode.children.forEach(child => {
dom.appendChild(render(child));
})
}
return dom;
}
const dom = render(vnode)
console.log(dom)