Vue3源码分析-完整update流程和diif算法
Posted 吐司FE
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue3源码分析-完整update流程和diif算法相关的知识,希望对你有一定的参考价值。
前言
在上一篇文章vue的首次渲染过程中提到在组件的首次渲染阶段会有一个副作用渲染函数setupRenderEffect
,在这个函数内会使用响应式API Effect
创建副作用函数componentEffect
,这里只需要简单的理解为,当组件内的数据改动时这个由Effect
包裹的componentEffect
就会重新调用,在这个函数内部会判断当前组件是处于首次渲染还是更新,当组件内数据发生变化时会进入到update
的分支,本文要看的diff
流程也就是从这里开始。
PS: 当前的
Vue
版本是3.0.5
。本文会忽略TELEPORT、SUSPENSE
等特殊vnode
类型,对于一些细微的优化也会忽略(比如patch函数的optimized参数
)。
回顾setupRenderEffect
在组件的数据发生改变时会自动触发副作用渲染函数setupRenderEffect
:
// runtime-core/src/renderer.ts
import { effect } from '@vue/reactivity'
const setupRenderEffect = (instance, initialVNode, container, ...) => {
// 在当前的组件实例上添加 update 方法,通过响应式方法 effect 创建
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
//...
} else {
// 非首次渲染,进入 update 阶段
let { next, vnode } = instance
// 缓存一份更新后的 vnode
let originNext = next
if (next) {
// 这里需要把更新后的组件 vnode 赋值 el,因为下面第一次 渲染新的组件 vnode 时并没有设置 el
next.el = vnode.el
updateComponentPreRender(instance, next)
} else {
// 如果没有 next 直接指向当前 vnode
next = vnode
}
// 渲染新的组件 vnode,因为数据发生了变化
const nextTree = renderComponentRoot(instance)
// 缓存旧的组件 vnode
const preTree = instance.subTree
// 更新实例上的 subTree 为新的组件 vnode
instance.subTree = nextTree
patch(prevTree, nextTree, ..., instance)
// 更新 DOM 元素
next.el = nextTree.el
}
}, prodEffectOptions)
这里主要看update
的部分(即else
代码块)。首先会先从组件实例instance
里取出next
(在处理组件一节会详细说明next
存在与不存在的情况)。
组件内的数据发生了改变,所以要生成新的组件模板的vnode
节点,在渲染阶段命名为subTree
,然后还要保存一份旧的subTree
,这样有了新旧subTree
后就可以用patch
函数更新DOM
。
patch函数
在进入具体的diff
流程之前,我们不妨先想一下,当数据发生改变时,会有哪些变化类型?
实际上按照类型可以分为更新普通DOM
元素和更新vue
组件这两种情况。下面先关注一下patch
函数的逻辑。
// runtime-core/src/renderer.ts
const patch = (n1, n2, container, ...) => {
// n1 是旧节点,n2 是新节点
// 如果 n1、n2 新旧节点的类型不同,直接销毁旧节点
if(n1 && !isSameVNodeType(n1, n2)) {
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
const { type, ref, shapeFlag } = n2
switch(type) {
// 处理文本节点
case Text:
processText(n1, n2, container, anchor)
break
// 处理注释
case Comment:
processCommentNode(n1, n2, container, anchor)
break
// ...
default:
// 处理 DOM 元素
if(shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container, ...)
// 处理 vue 组件
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(n1, n2, container, ...)
}
// ...
}
}
最开始先判断新旧节点的类型是否一样,如果不一样,可以设想某个节点从div
标签变成span
标签,最合理的方式是直接舍弃旧节点,重新挂载新的节点。
再往下会根据当前节点类型type
进行特定的处理。比如文本节点执行processText
的特定处理、注释节点执行processCommentNode
的特定处理。这样的前置处理实际上是一个优化,在编译阶段,vue
会将模版语法编译成渲染函数,这个时候会把第一个参数节点类型type
填上,如果这个参数命中了这样的特殊节点,会直接执行相应的process
方法。
default
块儿里才是分析的重点,即处理普通DOM
元素和vue
组件。
处理DOM元素
先举一个栗子
以上是关于Vue3源码分析-完整update流程和diif算法的主要内容,如果未能解决你的问题,请参考以下文章
vue3源码分析——实现组件通信provide,inject
vue3源码分析——实现组件通信provide,inject