Vue.js 源码(6)—— VNode

389 阅读3分钟

这是我参与更文挑战的第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;
}

注释节点只有两个有效属性,textisComment

一个真实的注释节点:

<!-- 注释节点 -->

对应的 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 元素。