这是我参与更文挑战的第6天,活动详情查看: 更文挑战。
前言
之前,我们介绍了虚拟 DOM。本文,我们将一起学习什么是VNode, VNode 的作用, 以及不同类型的 VNode 之间有什么区别。
什么是 VNode
顾名思义就是虚拟节点。我们可以使用它来实例化不同类型的 vnode 实例。不同类型的 vnode 实例各自表示不同类型的 DOM 元素。
DOM 元素有元素节点、文本节点和注释节点,相应的,vnode实例也有。
先看下 VNode 类长什么样
export default class VNode {
constructor(tag, data, children, text, elm, contexxtt, componentOptions, asyncFactory) {
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
this.elm = elm;
this.ns = undefined;
tthis.context = context;
this.functionalContext = undefined;
this.functionalOptions = undefined;
this.functionalScoped = undefined;
this.key = data && data.key;
this.componentOptions = componentOptions;
this.componentInstance = undefined;
this.parent = undefined;
this.raw = false;
this.isStatic = false;
this.isRootInsert = true;
this.isComment = false;
this.isCloned = false;
this.isOnce = false;
this.asyncFactory = asyncFactory;
this.asyncMeta = undefined;
this.isAsyncPlaceholder = false;
}
get child(){
return this.componentInstance
}
}
简单的说,vnode 可以理解为节点描述对象,它描述了怎样去创建真实的 DOM 节点。
vnode 表示一个真实的 DOM 元素,所有真实的 DOM 节点都使用 vnode 创建并插入到页面中:
graph LR
v[vnode] --create--> d[DOM] --insert--> view[视图]
上面的流程图展示了使用 vnode 创建真实 DOM 并渲染到视图的过程。
VNode 的作用
由于每次渲染视图时都是先创建 vnode,然后使用它创建真实 DOM 插入到页面中,所以可以将上一次渲染视图时所创建额的 vnode 缓存起来,之后每当需要重新渲染视图时,将新创建的 vnode 和上一次缓存的 vnode 对比,查看它们之间有哪些不一样的地方,找出这些不一样的地方并基于此去修改真实 DOM。
Vue.js 目前对状态的侦测策略采用了中等粒度,当某个状态发生改变时,只通知使用了这个状态的组件。
graph
w[web页面] === a[组件]
w === b[组件]
w === c[组件]
VNode 的类型
vnode 有很多中类型。
注释节点
创建注释节点
export const createEmptyNode = text => {
const node = new VNode();
node.text = text;
node.isComment = true;
return node;
}
注释节点只有两个有效属性,text
和 isComment
。
一个真实的注释节点:
<!-- 注释节点 -->
对应的 vnode 如下:
{
text: '注释节点',
isComment: true
}
文本节点
文本也很简单
export function createTextVNode(val){
return new VNode(undefined, undefined, undefined, String(val))
}
文本节点只有一个有效属性—— text
克隆节点
克隆节点是将现有节点的的属性复制到新节点中,让新创建的节点和被克隆的节点的属性保持一致,从而实现克隆效果。它的作用是优化静态节点和插槽节点。
以静态节点为例,当组件内的某个状态发生变化后,当前组件会通过虚拟 DOM 重新渲染视图,静态节点因为它的内容不会改变,所以除了首次渲染需要执行渲染函数获取 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;
vloned.isComment = vnode.isComment;
cloned.isCloned = true;
if (deep && vnode.children ) {
cloned.children = cloneVNodes(vnode.children)
}
return cloned;
克隆节点与被克隆节点之间唯一的区别是 isCloned
属性。
元素节点
元素节点通常存在以下4种有效属性:
- tag
- data(attrs、class、style等)
- children
- context
一个真实的元素节点:
<p><span>hello world</span></p>
对应的 vnode:
{
children: [VNode],
contextt: {...},
data: {...},
tag: 'p',
...
}
组件节点
组件节点和元素节点类似,有两个独有的属性:
componentOptions
componentInstance
一个组件节点:
<child></child>
对应的 vnode:
{
componentInstance: {...},
componentOptions: {...},
context: {...},
data: {...},
tag: 'child',
...
}
函数式组件
函数式组件可能大家用的不多,可以去官网回顾下。(函数式组件)
函数式组件和组件节点类似,它有两个独有的属性
functionalContext
functionalOptions
{
functionalContext: {...},
functionalOptions: {...},
context: {...},
data: {...},
tag: 'div',
...
}
总结
一句话,VNode 是一个类,可以生成不同类型的实例,不同类型的 vnode 表示不同类型的真实 DOM 元素。