Vue响应式之组件更新
# 前言
前面的文章说过,当数据发生变化时,会触发渲染 Watcher 的回调函数,进而执行组件更新。
本文就开始来讲讲这个组件更新到底做了哪些事,可能涉及的知识点有:
- 模板解析
- Virtual DOM
- VNode
- dom diff 过程
部分知识点只会简单点下,详细的再单独一篇文章讲解
# 从 updateComponent 入手
# dom diff
没看源码,先猜测下策略
如果是新旧节点不同,最简单的情况,做个替换即可
如果是新旧节点相同:值不变则结束处理,文本节点特殊处理,否则比较子节点,即两个列表做个对比
这里提到了一个节点是否相同的概念,猜测应该是判断 key 和节点类型
回到比较子节点,举例:
旧:ABCDE
新:CAFBDG
2
遍历新列表的每个节点,每次再去旧列表中找,找到了放对应位置(继续做判断新旧节点相同类型是否更新的操作),没找到则新建,时间复杂度O(n²)
一个个进行比对,不相同则替换,举例
// 旧
<div>
<A/>
<B/>
<C/>
<D/>
<E/>
</div>
2
3
4
5
6
7
8
还要注意一点,如果子节点不带 key 且节点类型相同,在判断节点相同的处理上判断这两个节点相同,保留了原节点的内部状态,产生一系列的 bug
因此子节点类型相同的情况,需要带 key ,带key还有个好处,一轮查询由O(n) 变为 O(1) 可以提高性能
举例
<body>
<div id="el">
</div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template" id="demo-template">
<div>
<test v-if="flag" name="s1">
</test>
<test v-else name="s2">
</test>
</div>
</script>
<script type="text/x-template" id="test-template">
<div>
{{dname}}
</div>
</script>
<script>
Vue.component('test', {
props: ['name'],
template: '#test-template',
data: function () {
return {
dname: this.name
}
},
})
var vm = new Vue({
el: '#el',
template: '#demo-template',
data: {
flag: true,
}
})
</script>
</body>
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
不带 key 的情况, 控制台执行 vm.$data.flag = false
去触发 patch
diff 比较时判断这前后节点一样,直接用了原来的节点了,导致内部状态不变, dname 还是原来的 s1
加上 key 后, patch 时就会认为这不是相同的节点,做个替换,然后就可以得到 s2 了
<test v-if="flag" name="s1" key="s1">
</test>
<test v-else name="s2" key="s2">
</test>
2
3
4
举例
ABD // 旧 vdom
=>
BADC // 新 vdom
BADC // 真实 dom
2
3
4
如何高效的进行移动呢?
新旧 vdom 各有一个头尾节点,进行四次比较,移动真实 dom 和指针
os = A oe = D
ns = B ne = C
2
四种比较都不满足,找到 ns 在旧 vdom 的节点相同的节点并做比较,没找到的话说明要新建 ns 节点
这个过程可能还用了 key map 这类的做加速,没 key 的话查找是 O(n) 的时间复杂度
# 父组件数据更新导致子组件变动,整个过程如下:
_render: 递归解析模板(不会深入解析组件),通过内部 createElement 方法生成 VNode
_update: 新旧 vnode 执行 patchVnode
patchVnode: vdom 的 diff 过程。若 vnode 不一致,对该 vnode 进行 _render 和 _update ,这是一个 dfs 的过程。若组件 vnode 不变,仅参数不同,则执行 prepatch ,将新参数传入组件,执行 updateChildComponent 对该组件进行更新
# vdom 更新的时候,真实 dom 是一次次被更新么?
看了下貌似是,如果是批量插入的,可能做了优化