四、虚拟节点
自己动手实践,就会更加深刻的理解
在前面,已经解决了属性的深层次渲染,接下来需要解决虚拟节点的问题——即将DOM转换为vnode。
01、虚拟节点
简单起见,简单起见,对于一个虚拟节点,有这么些属性:
- tag:表示节点的标签名
- data:表示节点的属性
- text:表示文本节点的文本(不为文本节点时,为undefined)
- type:节点的类型
- children:子节点
/**
* 虚拟节点
*/
class MyVNode {
/**
* 构造函数
* @param {string} tag 标签名
* @param {object} data 标签拥有的属性
* @param {string} text 文本节点中的文本(非文本节点则为undefined)
* @param {number} type 1代表元素节点,3代表文本节点
*/
constructor(tag, data, text, type) {
this.tag = tag && tag.toLowerCase();
this.data = data;
this.text = text;
this.type = type;
this.children = [];
}
/**
* 添加子节点
*/
appendChild(vnode) {
this.children.push(vnode);
}
}
02、将dom转换为vnode
这里只考虑元素节点(type=1)和文本节点(type=3),其他类型的节点还有:属性节点、注释节点、文档节点等,总共12中节点。
首先拿到一个节点的 nodeType, nodeName, nodeValue, attributes, childNodes 属性,然后根据 nodeType 的不同来生成相应的vnode,以及是否需要递归调用来添加子节点。
/**
* 将真实节点转换为虚拟节点
*/
function getVNode(node) {
const {nodeType, nodeName, nodeValue, attributes, childNodes} = node;
let _vnode = null;
switch (nodeType) {
case 1:
// 元素节点
const _attrObj = {};
for (const {nodeName, nodeValue} of attributes) {
_attrObj[nodeName] = nodeValue;
}
_vnode = new MyVNode(nodeName, _attrObj, undefined, nodeType);
for (const node of childNodes) {
_vnode.appendChild(getVNode(node));
}
break;
case 3:
// 文本节点
_vnode = new MyVNode(undefined, undefined, nodeValue, nodeType)
break;
}
return _vnode;
}
03、vnode转换为dom节点
同样根据type的值来生成相应的节点。
/**
* 将虚拟节点转换为真正DOM节点
* @param {MyVNode} vnode 虚拟节点
*/
function parseVNode(vnode) {
const { tag, type, data, text, children} = vnode;
let _node;
switch(type) {
case 1: // 元素节点
_node = document.createElement(tag);
// 属性
Object.entries(data).forEach(([key, value]) => {
_node.setAttribute(key, value);
})
// 递归生成子元素
for (const child of children) {
_node.appendChild(parseVNode(child));
}
break;
case 3:
_node = document.createTextNode(text);
break;
}
return _node;
}
04、效果图
html部分:
<article id="root">
<section title="name">romeo
<p>p1</p>
<p>p2</p>
</section>
<section title="message" id="message">wants to be rich</section>
</article>
js其余部分:
const root = document.querySelector('#root');
const vroot = getVNode(root);
const newRoot = parseVNode(vroot);
document.body.appendChild(newRoot)
可以看到内容成功的复制了一份:
源代码在github上,请点击阅读原文查看~
感兴趣的小伙伴请关注我的公众号~