Vue3中的虚拟Dom和patch算法

172 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

前言

Vue3对Vnode(虚拟dom)的结构做了调整,以适应编译器的优化策略,相对应的patch算法也会利用这些变化提高运行速度

新的Vnode结构

要了解新的Vnode结构,首先需要搞清楚以下的问题:

Vue3的虚拟Dom结构是在什么时候第一次生成

const vnode = createVNode({
    rootComponent as ConcreteComponent,
    rootProps
})
vnode.appContext = context

在第一次被挂载的时候生成,首次挂载创建根组件对应的Vnode,在后续需要再次执行该方法内部的渲染函数,再次渲染进行细化

虚拟Dom内部结构变化

  1. Vue2中Vnode的结构children只能是数组或者是空字符串,Vue3中的children即可以是数组也可以是文本

在Vue3中如果children是数组,即可代表内的每一个对象是一个元素,如果是字符串即可代表当前这个节点是文本。

  1. 在编译过程中如果打开了Block,当前的节点如果有动态的部分,则需要存储在dynamicChildren
  2. 动态属性,patchFlag补丁标记,保存将来有哪些内容是动态的(比如:属性、值、样式)
  3. shapeFlag标明当前节点基础的类型,可以清晰的返回当前的节点是什么类型,从而做不同的流程(switch语句是根据它来进行精准判断)
  4. type表明当前节点类型,在Vue2它是一个字符串(表明当前节点的名称),在Vue3中,它有两种类型,第一种是组件的配置对象,第二种是Symbol(Fragment)普通节点类型 1654335690413.png 1654336741840.png

过程

创建Vnode

mount()执行时,创建根组件Vnode

mount(rootContainer:HostElement,isHydrate:boolean):
 if(!isHydrate){
     const vnode = createVNode({})
 }

渲染Vnode

render(vnode,rootContainer)方法将创建的vnode渲染到根容器上

初始化patch

传入oldVnodenull,初始path为创建行为

patch(container._vnode || null,vnode,container)

使用mountConponentn2转换为dom

mountComponent(n2,container,anchr,parentComponent,parentSuspense,isSVG,optimized)

创建一个渲染副作用,执行render,获得Vnode之后,在执行patch转换为dom

setupRenderEffect(
    instance,initialVNode,container,anchor,parnetSuspense,isSVG,optimized
)

它在初始化阶段,核心任务是执行instance的render函数获取subTree

const subTree = (instance.subTree = renderComponentRoot(instance))

最后patch这个subTree

patch(subTree)

更新阶段

更新阶段,patch函数对比新旧Vnode,得出dom操作内容,componentEffect中会调用patch,并传入新旧两个Vnode

patch(subTree,prevTree,nextTree)

多个子元素更新

如果同时出现多个子元素,比如使用v-for时的情况,则需要重排操作,使用patchChildren更新

patchChildren(fragmentEndAnchor)

如果设置了key的情况下,则需要使用patchKeyedChildren

patchKeyedChildren(fragmentEndAnchor)