探索Vue渲染器的内部机制-文本节点

83 阅读3分钟

在前面的文章中我们只了解一种vnode类型,就是描述普通标签的vnode(元素节点),我们使用type属性来描述元素的名称,值是一个字符串的类型,即:

const vnode = {
    type:'p',
}

在Vue中其实可以用虚拟DOM描述很多类型的真实DOM,最常见的还有两种节点类型分别是文本节点和注释节点,注释节点又分为单行注释和多行注释,由于注释节点仅在模板中存在,并不会在渲染后的 DOM 中出现,所以这里只讨论文本节点,但注释节点和文本节点的处理方式是类似的。

那么应该如何使用虚拟DOM来描述一个文本节点呢?在之前的元素节点中,我们可以使用vnod.type的方式来对一个元素节点类型进行描述,它的值是一个字符串,是因为这个字符串值本身就代表了真实DOM标签的名称,但文本节点只是一段文字并没有与之对应的标签名称,所以我们需要针对这种情况人为的创造唯一标识并将其作为文本节点类型的属性值。

const Text = Symbol('text');
const vnode = {
    type:Text,
    children:'我是一个文本节点'
}

唯一标识我们可以很自然的想到使用Symbol来作为属性值,那么现在就可以用这个vnode来描述文本节点并且跟元素节点进行区分了。下面就将对文本节点进行渲染,再来修改一下渲染器中的patch函数。

function patch (oldVnode,newVnode,container) {
    const {type} = newVnode;
    if(typeof type == 'string'){
        if(!oldVnode){
            mountElement(newVnode,container)
        }else{
            // 打补丁...
        }
    }else if( type == Text){ // 说明描述的是文本节点
        if(!oldVnode){ // 文本节点的挂载阶段
            const el = newVnode.el = document.createTextNode(newVnode.children);
            container.appendChild(el);
        }else{ // 文本节点的更新阶段
            const el = newVnode.el = oldVnode.el;
            if(oldVnode.children != newVnode.children){
                el.nodeValue = newVnode.children;
            }
        }
    }
}

patch函数中我们首先对vnode中的type进行判断,如果是字符串类型说明要对元素节点进行操作,然后接着走挂载或更新元素节点的逻辑,如果跟我们声明的唯一标识相等说明要对文本节点进行操作,这时也需要区分挂载和更新阶段,如果没有oldVnode则代表是挂载阶段使用document.createTextNode(newVnode.children)方法来创建一个文本节点,需要注意此时还需要将创建出来的文本节点挂载到newVnode上,因为在下次更新阶段要用到,最后在appendChild到容器中。

如果已经存在oldVnode则表示当前处于文本节点的更新阶段中,这时候挂载阶段中的newVnode就变成了更新阶段的oldVnode,接着获取到当时添加在其身上创建的文本节点(oldVnode.el),然后通过判断两个文本节点的内容是否相同,不相同则修改原来oldVnode中的nodeValue属性为newVnode中的最新内容。更新阶段中也需要注意const el = newVnode.el = oldVnode.el给当前newVnode添加el属性,因为它代表的是初次创建的文本节点(初次挂载),后续的所有操作都需要基于文本节点本身,所以两个阶段中都必须这样做才能形成一个闭环。

以上就是在Vue渲染器中针对文本节点的挂载和更新。