实现Virtual DOM 的VNode节点
在这里,我们首先要区分Virtual DOM 和VNode的意思,Virtual DOM我之前也介绍过,是虚拟DOM,相信大家也不陌生,而VNode是虚拟节点的意思。好比说JavaScript是由一堆Node节点组成的DOM树。
另外,由于Virtual DOM是抽象的JavaScript对象,所以具备了跨平台能力,因为我们完全可以根据特定规则,去渲染出对应的任意的所需要的对象。
实现一个VNode
在着手编写代码前,我们先看下的JavaScript 节点都包含哪些信息。
hi
这里我写了一个a标签,可以看到,它包含了标签名、href属性、文本。当然,如果更复杂的一些,还会包含嵌套关系。
所以,我们可以想想,因为VNode是一个真实的DOM 节点的抽象,我们也需要这些参数:标签名、属性、文本、子标签等。
该方法
VNode
大概在Vue源码中736行,除了我们这里传递的参数外,其还包含了context
、componentOptions
和asyncFactory
。
class VNode{
constructor(tag, data, children, text, elm) {
// tag 标签名
this.tag = tag
// data 标签包含的数据信息,如前面的href属性,还有其他的如props等
this.data = data
// children 子节点, type:Array
this.children = children
// elm 当前虚拟节点对应的真实节点
this.elm = elm
}
}
然后我们在看来一个Vue组件。
I am span.
用刚才的VNode
类表示就是如下样子:
new VNode('span', {
// 指令集合数据
directives: [
{
// v-show 指令
rawName: 'v-show',
expression: 'isShow',
name: 'show',
value: true
}
],
// 静态class
staticClass: 'text'
// 因为文本节点也是节点,所以我们需要再次new一个VNode
[ new VNode(undefined, undefined, undefined, 'I am span.')]
})
转换成VNode以后的代码:
{
tag: 'span',
data: {
directives: [
{
rawName: 'v-show',
expression: 'isShow',
name: 'show',
value: 'true'
}
],
staticClass: 'text',
text: undefined,
children: [
{
tag: undefined,
data: undefined,
text: 'I am span.',
children: undefined
}
]
}
}
所以我们会发现,如果你的节点嵌套关系足够复杂,这里的VNode也会显得特别庞大。
另外,AST(抽象语法树,Abstract Syntax Tree),也和这个差不多。
进一步封装
通过观察上面的代码,我们可以发现,在创建VNode的过程中,会存在空节点和文本节点,这些节点都是很简单的,不需要一些属性,所以我们可以考虑将创建这两种节点的方法提取出来。
在Vue源码中,也有这两个方法, 分别是
createEmptyVNode
和createTextVNode
,大概在784-794行左右。
创建空节点
function createEmptyVNode() {
const node = new VNode();
node.text = ''
return node;
}
创建文本节点
function createTextVNode(val) {
return new VNode(undefined, undefined, undefined, String(val))
}
克隆一个VNode节点
有时候,我们需要一个一摸一样的VNode节点,此时,我们只需要提取出来一个克隆方法即可。
该方法
cloneVNode
在Vue源码中也存在,大概在802行左右。
function cloneVNode(node) {
const cloneVNode = new VNode(
node.tag,
node.data,
node.children,
node.text,
node.elm
);
return cloneVNode;
}
这里克隆节点,除了包含以上属性外,还应该包含如:节点注释、节点key等等。
总结
VNode就是一些虚拟的节点,从而构成了Virtual DOM。并且嵌套关系越深,构建出来的VNode对象也越复杂,这个过程中,由于存在空的节点和文本节点,这两个节点不需要额外的属性,所以我们将创建方法抽取了出来。