Vue3.x源码阅读笔记(三)-组件挂载
前两节讲了组件的创建和首次将DOM VNode挂载到页面的过程,其中当我们页面数据发生变化时候,会调用副作用渲染函数更新组件,这次来看下组件更新部分的逻辑,主要做了三件事情:
更新组件
VNode节点重新生成
subTree子树VNode根据新旧
VNode执行patch逻辑1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57// 运行副作用渲染函数
// packages/runtime-core/src/renderer.ts
const setupRenderEffect: SetupRenderEffectFn = (
// 组件实例
instance,
// 需要挂载的vnode
initialVNode,
// 挂载容器
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// 组件挂载
} else {
// 组件更新逻辑
let { next, bu, u, parent, vnode } = instance
// 新的组件vnode,涉及组件的更新策略
// 组件数据变化可能有两种,一种是组件本身的数据变化引起,next就不存在,将之前的vnode赋值给next。另外一种是父组件更新过程中遇到子组件节点
// 先判断子组件是否需要更新,如果需要先执行子组件的重新渲染方法,这种情况下next就是子组件的vnode
if (next) {
next.el = vnode.el
// 更新组件vnode节点信息
updateComponentPreRender(instance, next, optimized)
} else {
// 没有新的组件vnode,则将之前的vnode指向next
next = vnode
}
// 组件更新,重新根据组件实例生成subtree,组件里面的子组件也会生成新的subtree
const nextTree = renderComponentRoot(instance)
// 缓存旧的子树
const prevTree = instance.subTree
// 更新子树vnode
instance.subTree = nextTree
// 组件更新核心逻辑,根据新旧vnode做patch
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleport
// 如果在teleport中父节点可能已经改变,所以容器直接找旧的子树dom元素的父节点
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
// 参考节点在 fragment 的情况可能改变,所以直接找旧树 DOM 元素的下一个节点
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
// 缓存更新后的DOM节点
next.el = nextTree.el
}
}
}假设有以下的
Vue代码,我们来分析一下更新过程。
很简单,当我们点击“切换”按钮时候,会更新父组件的内容和子组件props的内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<div id="app">
<div @click="change">切换</div>
{{APP}}
<hello :num="APP"></hello>
</div>
// 子组件
const Hello = {
template:'<div>{{num}}</div>',
props:{
num:{
type:Number,
default:0
}
}
}
// 父组件
const App = {
components:{
Hello
},
setup() {
const APP = Vue.ref(1)
const change = () => {
APP.value = 2
}
return {
APP,
change
}
},
}更新组件
VNode节点1
2
3
4
5
6
7
8
9
10
11
12// 新的组件vnode,涉及组件的更新策略
// 组件数据变化可能有两种,一种是组件本身的数据变化引起,next就不存在,将之前的vnode赋值给next。另外一种是父组件更新过程中遇到子组件节点
// 先判断子组件是否需要更新,如果需要先执行子组件的重新渲染方法,这种情况下next就是子组件的vnode
let { next, bu, u, parent, vnode } = instance
if (next) {
next.el = vnode.el
// 更新组件vnode节点信息
updateComponentPreRender(instance, next, optimized)
} else {
// 没有新的组件vnode,则将之前的vnode指向next
next = vnode
}首先是更新组件的
VNode。这里判断的next是什么意思呢?这里可以简单说一下,后面会详细分析。
组件数据变化更新可能有两种:第一种是自身数据变化更新
DOM,这个时候next为空第二种是父组件更新过程中遇到子节点更新,比如说,我们在父组件给子组件传递了一个
props叫做num,然后我们在父组件中修改这个num的值,此时父子组件都会发生更新,第一次更新父组件时候,next就是空,执行到子组件,next就会有值。而且next的el是null。next就是下一次组件更新对应的VNode
这里vnode和next区别就是,vnode是当前的vnode,next是下一次更新的vnode。
当上面的例子我们点击切换时候,会触发到effect包裹的副作用渲染函数。第一次是父组件,此时next为空,我们继续往下看。
重新生成subTree子树VNode
这里执行renderComponentRoot()方法,将我们的组件VNode(initialVNode)生成为真实的DOM VNode(subTree),然后更新我们的instance中的subTree,再缓存我们之前的subTree去用这两个新老的subTree去做patch。
1 | |
根据新旧VNode执行patch逻辑
组件patch
组件更新时候,如果有子组件会先执行patch中的processComponent用来更新组件实例,比如Hello组件。
我们还是以App.vue中的Hello.vue组件为例,来看下更新时候的processComponent方法(这里的App.vue更新时候,实际会先执行Fragment,之后再看)
processComponent
如果我们的n1不存在就是挂载组件,否则就执行updateComponent来执行更新。
1 | |
updateComponent
首先通过shouldUpdateComponent来判断我们当前的子组件实例是否需要更新,这个方法会根据props,transition,dirs等一系列属性来判断是否需要更新,如果不需要更新,那么就会简单复制属性。
如果需要更新,此时会将新的组件VNode``n2赋值给instance.next,然后重新执行update()副作用渲染函数。
1 | |
再次重新执行update方法时候,可以看到,此时就会判断我们的next是否存在,next就是我们上面的n2,也就是新的组件vnode,此时会执行updateComponentPreRender
1 | |
这个方法接受一个我们当前的组件实例instance和下一次更新的组件VNode,将我们新组件VNode的component指向组件实例,然后组件实例的**vnode**设置为新的**vnode****,**然后更新props,slots等,因为在renderComponentRoot会使用到。
1 | |
DOM VNode的 patch
在上面重新执行完成将组件vnode生成subTree vnode之后,接下来就会执行普通元素vnode的patch流程。这里再次执行patch时候,我们的就会执行走到processElement这个流程。
processElement
1 | |
processElement,如果是存在n1,也就是旧的VNode,那么就是更新元素,执行patchElement
1 | |
patchElement
这里主要做了两件事情,一个是更新props,也就是组件中的style、class等。剩下就是通过patchChildren来更新子节点
1 | |
patchChildren
元素的子节点可能有三种情况,纯文本、vnode数组和空,那么就会有以下几种情况:
旧节点是数组
新节点都是数组-全量
diff``patchKeyedChildren新节点是空-删除旧节点
unmountChildren新节点是文本-删除旧节点
unmountChildren然后挂载hostSetElementText旧节点是空
新节点是文本-设置文本节点
hostSetElementText新节点也是空-跳过
新节点是数组,挂载
mountChildren旧节点是纯文本
新节点是纯文本,文本替换-
hostSetElementText新节点是空,删除旧的节点-
hostSetElementText(container, '')新节点是数组,先
hostSetElementText(container, ''),再mountChildren。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75const patchChildren: PatchChildrenFn = (
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized = false
) => {
const c1 = n1 && n1.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0
const c2 = n2.children
const { patchFlag, shapeFlag } = n2
// 子节点有三种情况,文本,数组和空
// 如果是文本节点
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
// text children fast path
// prevShapeFlag旧节点的,
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// 由数组到文本,删除之前的子节点
unmountChildren(c1 as VNode[], parentComponent, parentSuspense)
}
if (c2 !== c1) {
// 文本对比不同,替换为新文本
hostSetElementText(container, c2 as string)
}{
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { //如果之前是数组节点
// prev children was array
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { //现在也是数组节点,做全量diff
// two arrays, cannot assume anything, do full diff
patchKeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
// 之前是数组,现在是空节点,仅仅删除之前的子节点
// no new children, just unmount old
unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true)
}
} else {
// prev children was text OR null
// new children is array OR null
// 之前的子节点是文本节点或者为空
// 新的子节点是数组或者为空
if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {// 如果之前子节点是文本,则把它清空
hostSetElementText(container, '')
}
// mount new if array
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {// // 如果新的子节点是数组,则挂载新子节点
mountChildren(
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
}
}
}
}
本文标题:Vue3.x源码阅读笔记(三)-组件挂载
文章作者:Niuhk
发布时间:2022-05-26
最后更新:2022-07-23
原始链接:https://www.niuhk.cn/2022/05/26/Vue3.x源码阅读笔记(三)-组件更新/
版权声明:转载请注明出处!
分享