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 } = Vueconst 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 } = Vueconst 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 } = VuecreateApp({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.新增
