8.3 模拟渲染过程
接下来需要创建另一个类模拟将render
函数转换为Vnode
,并将Vnode
渲染为真实DOM
的过程,我们将这个类定义为Vn
,Vn
具有两个基本的方法createVnode, createElement
, 分别实现创建虚拟Vnode
,和创建真实DOM
的过程。
8.3.1 createVnode
createVnode
模拟Vue
中render
函数的实现思路,目的是将数据转换为虚拟的Vnode
,先看具体的使用和定义。
// index.html
<script src="diff.js">
<script>
// 创建Vnode
let createVnode = function() {
let _c = vn.createVnode;
return _c('div', { attrs: { id: 'test' } }, arr.map(a => _c(a.tag, {}, a.text)))
}
// 元素内容结构
let arr =
[{
tag: 'i',
text: 2
}, {
tag: 'span',
text: 3
}, {
tag: 'strong',
text: 4
}]
</script>
// diff.js
(function(global) {
class Vn {
constructor() {}
// 创建虚拟Vnode
createVnode(tag, data, children) {
return new VNode(tag, data, children)
}
}
global.vn = new Vn()
}(this))
这是一个完整的Vnode
对象,我们已经可以用这个对象来简单的描述一个DOM
节点,而createElement
就是将这个对象对应到真实节点的过程。最终我们希望的结果是这样的。
Vnode对象
渲染结果
8.3.2 createElement
渲染真实DOM
的过程就是遍历Vnode
对象,递归创建真实节点的过程,这个不是本文的重点,所以我们可以粗糙的实现。
class Vn {
createElement(vnode, options) {
let el = options.el;
if(!el || !document.querySelector(el)) return console.error('无法找到根节点')
let _createElement = vnode => {
const { tag, data, children } = vnode;
const ele = document.createElement(tag);
// 添加属性
this.setAttr(ele, data);
// 简单的文本节点,只要创建文本节点即可
if (util._isPrimitive(children)) {
const testEle = document.createTextNode(children);
ele.appendChild(testEle)
} else {
// 复杂的子节点需要遍历子节点递归创建节点。
children.map(c => ele.appendChild(_createElement(c)))
}
return ele
}
document.querySelector(el).appendChild(_createElement(vnode))
}
}
8.3.3 setAttr
setAttr
是为节点设置属性的方法,利用DOM
原生的setAttribute
为每个节点设置属性值。
class Vn {
setAttr(el, data) {
if (!el) return
const attrs = data.attrs;
if (!attrs) return;
Object.keys(attrs).forEach(a => {
el.setAttribute(a, attrs[a]);
})
}
}
至此一个简单的 数据 -> Virtual DOM
=> 真实DOM
的模型搭建成功,这也是数据变化、比较、更新的基础。