欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue使用虚拟dom进行渲染view的方法

程序员文章站 2022-06-25 13:02:21
前提 vue版本:v2.5.17-beta.0 触发render vue在数据更新后会自动触发view的render工作,其依赖于数据驱动;在数据驱动的工作下,每一个vu...

前提

vue版本:v2.5.17-beta.0

触发render

vue在数据更新后会自动触发view的render工作,其依赖于数据驱动;在数据驱动的工作下,每一个vue的data属性都被监听,并且在set触发时,派发事件,通知收集到的依赖,从而触发对应的操作,render工作就是其中的一个依赖,并且被每一个data属性所收集,因此每一个data属性改变后,都会触发render。

vue更新监听

看一段代码

// 来自mountcomponent函数
updatecomponent = function () {
 vm._update(vm._render(), hydrating);
};

new watcher(vm, updatecomponent, noop, {
 before: function before () {
 if (vm._ismounted) {
  callhook(vm, 'beforeupdate');
 }
 }
}, true /* isrenderwatcher */);

updatecomponent是更新组件的函数,内部调用vm._update,并且传参vm._render();

  • vm._render()返回了什么?看源码则得知返回了虚拟dom(vnode)
  • vm._update函数又做了什么事情?顾名思义,更新传入的vnode
  • 什么时候触发updatecomponent函数?在任何vue的data属性更改值都会触发

更新view

阅读_update函数得知,最终调用了vm.__patch__方法,最终找到为createpatchfunction方法的返回值

var patch = createpatchfunction({ nodeops: nodeops, modules: modules });

vue.prototype.__patch__ = inbrowser ? patch : noop; 

接下来重点看createpatchfunction的返回函数patch.

如果新的vnode不存在,则注销旧的vnode

if (isundef(vnode)) {
 if (isdef(oldvnode)) { invokedestroyhook(oldvnode); }
 return
}

如果旧的vnode不存在,则创建新的vnode

if (isundef(oldvnode)) {
 // empty mount (likely as component), create new root element
 isinitialpatch = true;
 createelm(vnode, insertedvnodequeue);
} 

如果以上不成立,则新的vnode和oldvnode都存在.如果oldvnode不是真实的dom,则为虚拟dom节点,并且新老vnode相似,则进行diff算法

var isrealelement = isdef(oldvnode.nodetype);
if (!isrealelement && samevnode(oldvnode, vnode)) {
 // patch existing root node
 patchvnode(oldvnode, vnode, insertedvnodequeue, removeonly);
}

如果新老vnode不同,则拿到oldvnode的父节点,辅助创建vnode的新节点

var oldelm = oldvnode.elm;
var parentelm = nodeops.parentnode(oldelm);

// create new node
createelm(
 vnode,
 insertedvnodequeue,
 // extremely rare edge case: do not insert if old element is in a
 // leaving transition. only happens when combining transition +
 // keep-alive + hocs. (#4590)
 oldelm._leavecb ? null : parentelm,
 nodeops.nextsibling(oldelm)
);

以上的步骤发现,更新view时,重点进入到了patchvnode函数,因此下面进入patchvnode的函数阅读

如果新老node相等,则不做处理

if (oldvnode === vnode) {
 return
}

如果vnode不是文本节点则有几种情况

if (isdef(oldch) && isdef(ch)) {
 // 如果oldvnode和vnode的children都有值(组件层),并且不想等,则执行更新children流程
 if (oldch !== ch) { updatechildren(elm, oldch, ch, insertedvnodequeue, removeonly); }
} else if (isdef(ch)) {
 // 如果vnode的children有值,如果当前dom有文本则清空,
 // 并将oldvnode的dom作为父节点,创建新vnode的children节点
 if (isdef(oldvnode.text)) { nodeops.settextcontent(elm, ''); }
 addvnodes(elm, null, ch, 0, ch.length - 1, insertedvnodequeue);
} else if (isdef(oldch)) {
 // 如果oldvnode存在children,但是新的没有,则删除oldvnode的children的vnode
 removevnodes(elm, oldch, 0, oldch.length - 1);
} else if (isdef(oldvnode.text)) {
 // 如果oldvnode有文本信息,则将dom的文本清空
 nodeops.settextcontent(elm, '');
}

如果vnode是文本节点, 则当新老节点文本不同,将dom的文本更新成新vnode的文本

else if (oldvnode.text !== vnode.text) {
 nodeops.settextcontent(elm, vnode.text);
} 

patchvnode函数处理的情况梳理一下则为:

  • 如果新老vnode相同,不作处理
  • 如果新vnode是文本节点,并且新老文本不同,将dom更新为vnode的文本
  • 如果新老vnode都有children,表示他们是组件层,则调用updatechildren去做组件层更新
  • 如果新vnode是组件层,oldvnode不是,则将当前dom添加新vnode组件的子元素
  • 如果oldvnode是组件层,新vnode不是,则操作dom,将oldvnode包含的子元素删除
  • 如果新vnode是组件层,oldvnode是文本节点,则将dom的文本清空

在patchvnode部分又浮现了一个新的函数:updatechildren,是在新老vnode都不是文本节点时调用的,这里就是vue的渲染机制的核心

updatechildren

updatechildren中将新老vnode的children进行的循环处理,每一次循环去判断是否有相同的vnode,如果没有则查找当前新vnode的子节点的key是否存在oldvnode的children中,如果不存在在这存在但已经不相同则创建新的dom,否则,如果是新老节点相同,回调patchvnode函数去处理2个节点。 这样进行了递归处理,组件层的更新就完成了。

结尾

本文为看源码分析vue更新机制部分,省略了特殊场景的源码分析,比如ssr、静态组件等。

总结

以上所述是小编给大家介绍的vue使用虚拟dom进行渲染view的方法,希望对大家有所帮助