手写场景 | 虚拟DOM转真实DOM

准备工作

虚拟DOM是一种轻量级的JS对象,包含以下属性:

  • tag:标签名
  • attrs:属性,一般有id class name等等
  • children:子节点的标签名属性子节点等…
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
26
27
28
29
30
31
// 虚拟DOM
const vnode = {
tag:'DIV',
attrs: {
id: 'app',
},
children: [
{
tag: 'SPAN',
children: [
{tag: 'A', children: []}
],
},{
tag: 'SPAN',
children: [
{tag: 'A', children: []},
{tag: 'A', children: []},
],
}
]
}


// 真实DOM
<div id = 'app'>
<span><a></a></span>
<span>
<a></a>
<a></a>
</span>
</div>

实现思路

  • 获取父节点的tag属性,通过document.createElement生成节点
  • 若有属性,则通过setAttrubite(key, value)添加属性
  • 如有子节点,则遍历子节点,重复步骤1,2
  • 把子节点通过appendChild添加到父节点

注意:最后会遇到节点为文本的情况,则可以通过createTextNode方式转义HTML字符,如果是number,可以先转换成string

JS实现

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
26
27
function myRender(vnode) {
// 如果是number类型
if (typeof vnode === 'number') {
vnode = String(vnode);
}

// 如果是string类型
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}

// 如果是普通的dom
let dom = document.createElement(vnode.tag);

if (vnode.attrs) {
for (let key of vnode.attrs) {
dom.setAttribute(key, vnode.attrs[key]);
}
}

// 子数组递归
if (vnode.children.length) {
vnode.children((child) => dom.appendChild(myRender(child)));
}

return dom;
}