vue3源码剖析 02
学习目标
- 编译器原理
- vue3编译过程剖析
- vue3编译优化策略
- vue3 patch算法剖析
编译器原理
template => ast => render
模板
抽象语法树
渲染函数
Vue3编译过程剖析
测试代码
<div id="app">
{{foo}}
</div>
<script src="../dist/vue.global.js"></script>
<script>
const { createApp, reactive } = Vue
const app = createApp({
data () {
return {
foo: 'foo'
}
},
}).mount('#app')
</script>
整体流程
template获取
app.mount()获取了template,vue/index.ts
编译template
compile将传入template编译为render函数,component.ts
实际执行的是baseCompile,compiler-dom/src/index.ts
第一步解析-parse:解析字符串template为抽象语法树ast
第二步转换-transform:解析属性、样式、指令等
第三步生成-generate:将ast转换为渲染函数
编译优化
静态节点提升
补丁标记和动态属性记录
缓存事件处理程序
块 block
Vue3虚拟dom和patch算法
vue3对vnode结构做了调整以适应编译器的优化策略,相对应的patch算法也会利用这些变化提高运行
速度
新的vnode结构
测试代码
patch.html
<div id="app">
<h1>patch</h1>
<p>{{foo}}</p>
</div>
<script src="../dist/vue.global.js"></script>
<script>
const { createApp } = Vue
const app = createApp({
data () {
return {
foo: 'foo'
}
}
}).mount('#app')
setTimeout(() => {
app.foo = 'foooooooooooo'
}, 1000);
</script>
创建VNode
mount()执行时,创建根组件VNode,packages/runtime-core/src/apiCreateApp.ts
渲染VNode
render(vnode, rootContainer)方法将创建的vnode渲染到根容器上。
初始patch
传入oldVnode为null,初始patch为创建行为。
使用mountComponent将n2转换为dom
创建一个渲染副作用,执行render,获得vnode之后,在执行patch转换为dom
setupRenderEffect在初始化阶段核心任务是执行instance的render函数获取subTree
最后patch这个subTree
更新流程
更新阶段,patch函数对比新旧vnode,得出dom操作内容。
componentEffect中会调用patch,并传入新旧两个vnode’
多个子元素更新
如果同时存在多个子元素,比如使用v-for时的情况:
<div id="app">
<div v-for="item in arr" :key="item">{{item}}</div>
</div>
<script src="../dist/vue.global.js"></script>
<script>
const { createApp, h } = Vue
createApp({
data () {
return {
arr: ['a', 'b', 'c', 'd']
}
},
mounted () {
setTimeout(() => {
this.arr.splice(1, 0, 'e')
}, 1000);
},
}).mount('#app')
</script>
典型的重排操作,使用patchChildren更新
设置了key的情况下,走patchKeyedChildren
// ['a', 'b', 'c', 'd']
// ['a', 'e', 'b', 'c', 'd']
// 1.从开始同步:掐头
// ['b', 'c', 'd']
// ['e', 'b', 'c', 'd']
// 2.从结尾同步:去尾
// []
// ['e']
// 3.新增