1、执行下面代码
声明响应式数据,且过1秒钟后,更改一次,看它的更新流程
<script src="../../dist/vue.global.js"></script>
<div id="demo">
<section>
<h1>{{count}}</h1>
</section>
</div>
<script>
const { createApp, ref } = Vue
var app = createApp({
setup() {
const count = ref(1)
setInterval(() => {
debugger
count.value++
}, 1000);
return { count }
}
})
app.mount('#demo')
</script>
2、打断点技巧
runtime-dom/src/nodeOps.ts 下setElementText内打上断点
在html代码setInterval内部打上断点
3、调用栈分析
首次触发断点时,如下如所示,在渲染视图时候,count依然为1
跳到下一个断点,如下图,此时触发了setup内部的setInterval函数,再调到下一个断点看发生了什么
- 如下图,此时在访问响应式数据count时候,会触发get和set,
- 在get收集依赖,set触发依赖更新,
- 在这里get因为没有activeEffect就导致不会收集它的依赖,至于为什么,在下面会有单独的说明,
- 再看看set触发依赖更新,此时的依赖其实就是componentUpdateFn,内部执行了 patch(prevTree,nextTree)
- 那么此时有个问题,我们什么时候收集的和count相关的componentUpdateFn依赖的呢?
- 这时候需要我们给响应式依赖的依赖收集打上断点看他是什么时候给我收集的依赖
reactivity/src/effect.ts 下trackEffect函数内打上断点,刷新页面
如下图可以看到,是在初次render时候,访问到了count所以会收集到依赖,但是在依赖收集的时候是收集的activeEffect,那么activeEffect是哪里来的,
在这个文件下可以找到ReactiveEffect函数,在new 这个函数的时候,我们会将实例赋值给全局变量activeEffect,所以此时的activeEffect是首次执行componentUpdateFn内new出来的实例
4、为什么只有写在模板内的响应式数据才会收集依赖,但是写在setup内的响应式数据却不会触发依赖收集呢???
reactivity\src\ref.ts 给RefImpl内的get打上断点,如图所示
此时会触发两次依赖收集
第一次是在render时候,依赖收集成功
第二依赖收集,在执行trackRefValue时候,此时的activeEffect为undefined
此时有个疑问activeEffect什么时候给我重置为undefined的,给ReactiveEffect内的run函数分别在这两个位置打上断点,如图所示,在执行render时候,run执行完了会执行finally下的activeEffect = this.parent
5、初始化挂载流程图如下
6、patch更新细节
案例一
<script src="../../dist/vue.global.js"></script>
<div id="app">
<h1>vue3 更新流程</h1>
<p>{{count}}</p>
<comp></comp>
</div>
<script>
var app = createApp({
data() {
return {
count: 1
}
},
mounted() {
this.count++
},
components: {
comp: {
template: '<div>comp-comp</div>'
}
}
})
app.mount('#app')
</script>
案例二
<script src="../../dist/vue.global.js"></script>
<div id="app">
<h1>vue3 更新流程</h1>
<p>{{count}}</p>
<comp></comp>
</div>
<script>
var app = createApp({
data() {
return {
count: 1
}
},
mounted() {
this.count++
},
components: {
comp: {
template: '<div>comp-comp</div>'
}
}
})
app.mount('#app')
</script>
案例三
<script src="../../dist/vue.global.js"></script>
<div id="app">
<div v-for="item in arr" :key="item">{{item}}</div>
</div>
<script>
var { createApp } = Vue
var app = createApp({
data() {
return {
arr: ['a', 'b', 'c', 'd', 'e']
}
},
mounted() {
setTimeout(() => {
this.arr.splice(2, 0,'f')
}, 1000);
}
})
app.mount('#app')
</script>
- 案例三打断点
packages\runtime-core\src\renderer.ts
文件下:- 搜索
patchKeyedChildren(
和patchUnkeyedChildren(
都打上断点 - 触发patchKeyedChildren函数
- sync from start 相同的头部
- sync from end 相同的尾部
- common sequence + mount 同一序列,新增未知节点
- common sequence + unmount 同一序列,删除未知节点
- unknown sequence
export function isSameVNodeType(n1, n2) {
return n1.type === n2.type && n1.key === n2.key // type是标签名 key是绑定的值
}
流程图
ProcessOn