这篇文章应该是vue3源码阅读系列的最后一篇了,今天学习的是关于vue3的虚拟dom以及它的diff算法。虚拟dom也是vue里比较重要的一个部分,其实三大框架里,react和vue采用的是虚拟dom,angular采用的还是真实的dom。虚拟dom也是由react先提出的。今天就是主要来学习一下vue3里虚拟dom的源码以及它的diff算法
什么是虚拟dom&&为什么?
我们知道,在浏览器上操作dom是比较消耗性能的,虚拟dom就是用js去生成和控制dom,这样能提高我们的页面性能以及让我们做dom操作时更加的方便。他的本质呢就是一个js对象,来描述我们的dom结构,去生成真实的dom。
除了性能,还有兼容性的问题,在操作dom的时候难免会有兼容性问题,但是如果使用虚拟dom的时候,我可以在js中先处理这些兼容性问题,再进行操作。
当然,虚拟dom还可以跨平台,因为dom是由js维护的,可以由各个平台去做适配。比如小程序。
源码阅读
虚拟dom部分
首先,关于虚拟dom的源码从哪看起呢?当然是从mount看起,在我们挂载vue的时候,肯定会把我们的模版进行渲染,这里就用到了虚拟dom
// 初始化vnode
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
可以看到mount里第一句就是创建vnode,这个vnode就是虚拟dom。接着我们去看这个vnode是怎么生成的。我们找到createVNode这个函数。
const vnode: VNode = {
__v_isVNode: true,
[ReactiveFlags.SKIP]: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
children: null,
component: null,
suspense: null,
ssContent: null,
ssFallback: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
}
我们可以看到这个函数的返回值就是个vnode对象,这个就是虚拟dom,里面包含里一些dom结构的信息。而mount就是先通过这个函数,根据我们不同的配置去生成这么一个vnode对象。之后呢这个vnode会通过patch这个函数来处理。这个之前在看初始化流程的相关代码时已经看过,当时看到这个patch方法就没看下去了,就知道它是个处理vnode的函数。我们看看patch这个方法干了啥。从源码里发现,patch会对你传进来的vnode进行判断,在初始化的时候会走一次renderComponentRoot这个方法。
...
// 执行组件的render函数,此处的result就是vnode
result = normalizeVNode(
render!.call(
proxyToUse,
proxyToUse!,
renderCache,
props,
setupState,
data,
ctx
)
)
...
return result
这里源码比较长,简单来说就是这个方法会执行根组件实例的render函数,获取整个app对应的vdom。
const subTree = (instance.subTree = renderComponentRoot(instance))
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
后来我们发现,生成了vdom之后,又通过patch去遍历,这样把children也去生成了。之后的流程就比较简单,通过createElement去生成真实dom就行。
diff
当数据变化时,会生成一个新的vnode,所以它会再执行一下patch函数,把新旧两个vnode都放进去
patch(
prevTree, // 旧
nextTree, // 新
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
还是根据流程走下去,我们找到了patchElemnt这个函数,这里就看最简单的文本变化
// text
// This flag is matched when the element has only dynamic text children.
if (patchFlag & PatchFlags.TEXT) {
if (n1.children !== n2.children) {
hostSetElementText(el, n2.children as string)
}
}
当文本发生变化时,通过调用hostSetElementText去生成新的dom。在整个程序非常庞大时,其实内部也是一样的,当数据变化时,先判断出变化了什么,然后通过不同的函数去生成dom。
Diff算法只是为了虚拟DOM比较替换效率更高,通过Diff算法得到diff算法结果数据表(需要进行哪些操作记录表)。原本要操作的DOM在vue这边还是要操作的,只不过用到了js的DOM fragment来操作dom(统一计算出所有变化后统一更新一次DOM)进行浏览器DOM一次性更新。其实DOM fragment我们不用平时发开也能用,但是这样程序员写业务代码就用把DOM操作放到fragment里,这就是框架的价值,程序员才能专注于写业务代码。