前文提到,vue使用虚拟DOM中主要做了两件事,一个是创建了VNode来模拟DOM树,另一个是通过patch算法比对新旧DOM的变更状态。本节主要介绍VNode相关,包括VNode是什么?VNode有什么作用?
6.1 VNode是什么
VNode是一个类,使用他可以实例化各种类型的vnode实例,不同的DOM元素对应不同类型的vnode。
class VNode{
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag // 当前节点标签名
this.data = data // 当前节点数据(VNodeData类型)
this.children = children // 当前节点子节点
this.text = text // 当前节点文本
this.elm = elm // 当前节点对应的真实DOM节点
this.ns = undefined // 当前节点命名空间
this.context = context // 当前节点上下文
this.fnContext = undefined // 函数化组件上下文
this.fnOptions = undefined // 函数化组件配置项
this.fnScopeId = undefined // 函数化组件ScopeId
this.key = data && data.key // 子节点key属性
this.componentOptions = componentOptions // 组件配置项
this.componentInstance = undefined // 组件实例
this.parent = undefined // 当前节点父节点
this.raw = false // 是否为原生HTML或只是普通文本
this.isStatic = false // 静态节点标志 keep-alive
this.isRootInsert = true // 是否作为根节点插入
this.isComment = false // 是否为注释节点
this.isCloned = false // 是否为克隆节点
this.isOnce = false // 是否为v-once节点
this.asyncFactory = asyncFactory // 异步工厂方法
this.asyncMeta = undefined // 异步Meta
this.isAsyncPlaceholder = false // 是否为异步占位
}
// 容器实例向后兼容的别名
get child (): Component | void {
return this.componentInstance
}
}
不同类型的vnode有不同的属性,更具体的说法是:不同类型的vnode有不同的有效属性。当使用VNode实例化一个vnode时,通过参数为实例设置属性时,无效的属性会默认被赋值为undefined或false。
根据DOM元素种类的不同,一共有六种vnode类型,分别是:注释节点、文本节点、元素节点、组件节点、函数式组件、克隆节点。
6.1.1 注释节点
创建一个注释节点:
export const createEmptyVNode = (val) => {
const node = new VNode()
node.text = 'val'
node.isComment = true
return node
}
一个注释节点只有两个有效属性,text和isComment,其余属性是默认值undefined和false。
举例一行注释代码:
这行代码对应的vnode为:
{
text: "我是注释",
isComment: true
}
6.1.2 文本节点
创建一个文本节点:
export const createTextVNode = (val) => {
return new VNode(undefined, undefined, undefined, String(val))
}
文本节点只有一个有效属性就是 text
:
{
text: '我是文本',
}
6.1.3 克隆节点
克隆节点即字面意思,将一个节点克隆到一个新节点上面,好处是可以优化静态节点和插槽节点。静态节点除了首次渲染需要执行渲染函数获取vnode之外,后续更新不需要执行渲染函数重新生成vnode,这时可以使用克隆节点方法将vnode克隆一遍,然后使用克隆节点进行渲染,这样就不需要重新执行渲染函数生成静态节点的vnode,从而提升一定的性能。
创建一个克隆节点:
export function cloneVNode(vnode,deep){
const cloned=new VNode(
vnode.tag,
vnode.data,
vnode.children,
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions,
vnode.asyncFactory
)
cloned.ns=vnode.ns
cloned.isStatic=vnode.isStatic
cloned.key=vnode.key
cloned.isComment=vnode.isComment
cloned.isCloned=vnode.isCloned
if(deep&&vnode.children){
cloned.children=cloneVNode(vnode.children)
}
return cloned;
}
克隆节点与被克隆节点的区别是isCloned属性。
6.1.4 元素节点
哈哈哈
上述代码对应的vnode为:
{
tag: 'div',
data: {
class: 'demo',
},
children: [
{
tag: 'span',
data: {
class:'test'
},
text: '哈哈哈'
}
]
}
元素节点有四个有效属性:
- tag:节点的名称,如p div span 等
- data:元素的属性 如class attrs style等
- children:当前节点的子节点列表
- context:当前组件的Vue.js实例
6.1.5 组件节点
组件节点与元素节点类似,多了两个个独有属性:
- componentOptions:组件节点的选项参数,包括propsData,tag,children等
- componentInstance:组件的实例,即Vue.js的实例
6.1.6 函数式组件
函数式组件与组件节点类似,额外多了两个独有属性:
- functionContext
- functionOptions
6.2 VNode有什么作用
渲染视图时,先模拟真实的DOM元素创建vnode,使用vnode渲染DOM节点,在每次渲染时,先将vnode缓存起来,下一次渲染时,通过比对新旧vnode的不同来修改真实的DOM元素。