patch中组件的更新解析
# 概览
在processComponent
处理组件方法中,若存在旧节点n1
,则是调用updateComponent
方法更新组件。本文会介绍updateComponent
方法的全流程。
# 源码解析
# updateComponent
组件更新
updateComponent
方法接受三个参数:旧虚拟节点n1
、新虚拟节点n2
和是否需要优化optimized
。该方法内部会先从节点n1
中获取组件实例,并同时赋值给新节点n2
的component
,保证新旧节点共享同一个组件实例。然后调用shouldUpdateComponent
方法判断是否需要更新组件;若需要更新,则继续判断组件实例上是否存在异步依赖并且实例尚未解析完成,若是,则调用updateComponentPreRender
进行组件预渲染,然后返回;若不存在异步依赖或者存在依赖且实例已经解析完成,则将新节点n2
赋值给实例的next
属性,然后调用实例的update
方法触发组件的重新渲染,该update
方法实际上就是运行finishSetupComponent
中的componentUpdateFn
;若不需要更新组件,则新节点n2
的DOM元素直接复用旧节点n1
的DOM元素,并且将实例的vnode
设置为n2
。
const updateComponent = (n1, n2, optimized) => {
const instance = n2.component = n1.component;
if (shouldUpdateComponent(n1, n2, optimized)) {
if (instance.asyncDep && !instance.asyncResolved) {
updateComponentPreRender(instance, n2, optimized);
return;
} else {
instance.next = n2;
instance.update();
}
} else {
n2.el = n1.el;
instance.vnode = n2;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 辅助方法
updateComponent
方法中主要用到shouldUpdateComponent
和updateComponentPreRender
两个辅助方法。关于updateComponentPreRender
在组件的挂载中提过,该方法主要就是更新props
和slots
,以及调用flushPreFlushCbs
处理前置队列中的任务。
# shouldUpdateComponent
shouldUpdateComponent
方法会返回一个布尔值,表示是否需要更新组件。
function shouldUpdateComponent(prevVNode, nextVNode, optimized) {
// 解构旧虚拟节点n1
const { props: prevProps, children: prevChildren, component } = prevVNode;
// 解构新虚拟节点n2
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode;
const emits = component.emitsOptions;
// 若新节点上有指令和过渡效果,则直接返回true,表示需要更新
if (nextVNode.dirs || nextVNode.transition) {
return true;
}
// 如果启用了优化模式,并且patchFlag存在
if (optimized && patchFlag >= 0) {
// 若是动态插槽,则返回true
if (patchFlag & 1024) {
return true;
}
// 若是全量props比较
if (patchFlag & 16) {
// 若旧节点上不存在属性,则判断新节点上是否存在属性,若存在则返回true,不存在,则返回false
if (!prevProps) {
return !!nextProps;
}
// 若旧节点上存在属性,则调用hasPropsChanged比较新旧props是否相等
return hasPropsChanged(prevProps, nextProps, emits);
} else if (patchFlag & 8) {
// 若存在动态props,
const dynamicProps = nextVNode.dynamicProps;
// 则遍历动态props数组,若发现属性值变化且不是事件监听器,则返回true
for (let i = 0; i < dynamicProps.length; i++) {
const key = dynamicProps[i];
if (nextProps[key] !== prevProps[key] && !isEmitListener(emits, key)) {
return true;
}
}
}
} else {
// 非优化模式处理
// 若旧节点子节点或者新节点子节点存在
if (prevChildren || nextChildren) {
// 则判断,若新节点子节点不存在或者新节点子节点不稳定,则返回true
if (!nextChildren || !nextChildren.$stable) {
return true;
}
}
// 若新旧props相同,则返回false
if (prevProps === nextProps) {
return false;
}
// 若旧props不存在,则判断新props是否存在,若存在则需要更新;否则不需要更新
if (!prevProps) {
return !!nextProps;
}
// 若新props不存在,则返回true
if (!nextProps) {
return true;
}
// 否则调用hasPropsChanged比较新旧props是否相等
return hasPropsChanged(prevProps, nextProps, emits);
}
// 默认返回false,表示不需要更新
return false;
}
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
shouldUpdateComponent
方法会根据optimized
采取不同的策略判断,若是优化模式,则根据新节点的patchFlag
判断是否需要更新;若不是优化模式,则基于新旧节点的props
/children
进行比较,判断是否需要更新;
设计特点与优化策略
PatchFlag
优化:
- 使用位运算快速判断更新类型
- 减少不必要的全量比较
- 动态
Props
只需检查特定属性
- 事件监听器优化
- 跳过事件监听器属性的比较
- 事件监听器变化不影响组件渲染
子节点稳定性检查
$stable
标志避免不必要的子组件更新- 静态内容可标记为稳定
引用相等性短路
- 相同引用直接跳过更新
动态插槽优化
- 动态插槽变化需要更新组件
- 静态插槽可跳过更新
过渡效果处理
- 过渡组件需要特殊处理
- 确保动画正确执行
# hasPropsChanged
hasPropsChanged
方法用于新旧属性是否相等,若不等,则返回true
,表示属性发生了改变。
function hasPropsChanged(prevProps, nextProps, emitsOptions) {
const nextKeys = Object.keys(nextProps);
// 先判断新旧属性的长度,即属性键的个数是否相等,若不等,则返回true
if (nextKeys.length !== Object.keys(prevProps).length) {
return true;
}
// 遍历旧属性的每个键,若该键的值与其在新属性的值不等,且不是事件监听器,则返回true,
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i];
if (nextProps[key] !== prevProps[key] && !isEmitListener(emitsOptions, key)) {
return true;
}
}
// 默认返回 false
return false;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16