vue3源码剖析 02

学习目标


  • 编译器原理
  • vue3编译过程剖析
  • vue3编译优化策略
  • vue3 patch算法剖析

编译器原理


template => ast => render

模板

image.png

抽象语法树

image.png

渲染函数

image.png

Vue3编译过程剖析


测试代码

  1. <div id="app">
  2. {{foo}}
  3. </div>
  4. <script src="../dist/vue.global.js"></script>
  5. <script>
  6. const { createApp, reactive } = Vue
  7. const app = createApp({
  8. data () {
  9. return {
  10. foo: 'foo'
  11. }
  12. },
  13. }).mount('#app')
  14. </script>

整体流程

image.png

template获取

app.mount()获取了template,vue/index.ts
image.png

编译template

compile将传入template编译为render函数,component.ts
image.png

实际执行的是baseCompile,compiler-dom/src/index.ts
第一步解析-parse:解析字符串template为抽象语法树ast
image.png
image.png

第二步转换-transform:解析属性、样式、指令等
image.png

第三步生成-generate:将ast转换为渲染函数
image.png

编译优化

静态节点提升

image.png

image.png

补丁标记和动态属性记录

image.png

image.png

缓存事件处理程序

image.pngimage.png

块 block

image.png

image.png

Vue3虚拟dom和patch算法


vue3对vnode结构做了调整以适应编译器的优化策略,相对应的patch算法也会利用这些变化提高运行
速度

新的vnode结构

image.png
image.png

测试代码

patch.html

  1. <div id="app">
  2. <h1>patch</h1>
  3. <p>{{foo}}</p>
  4. </div>
  5. <script src="../dist/vue.global.js"></script>
  6. <script>
  7. const { createApp } = Vue
  8. const app = createApp({
  9. data () {
  10. return {
  11. foo: 'foo'
  12. }
  13. }
  14. }).mount('#app')
  15. setTimeout(() => {
  16. app.foo = 'foooooooooooo'
  17. }, 1000);
  18. </script>

创建VNode

mount()执行时,创建根组件VNode,packages/runtime-core/src/apiCreateApp.ts
image.png

渲染VNode

render(vnode, rootContainer)方法将创建的vnode渲染到根容器上。
image.png

初始patch

传入oldVnode为null,初始patch为创建行为。
image.png

使用mountComponent将n2转换为dom
image.png

创建一个渲染副作用,执行render,获得vnode之后,在执行patch转换为dom
image.png

setupRenderEffect在初始化阶段核心任务是执行instance的render函数获取subTree
image.png

最后patch这个subTree
image.png

更新流程

更新阶段,patch函数对比新旧vnode,得出dom操作内容。

componentEffect中会调用patch,并传入新旧两个vnode’
image.png

多个子元素更新

如果同时存在多个子元素,比如使用v-for时的情况:

  1. <div id="app">
  2. <div v-for="item in arr" :key="item">{{item}}</div>
  3. </div>
  4. <script src="../dist/vue.global.js"></script>
  5. <script>
  6. const { createApp, h } = Vue
  7. createApp({
  8. data () {
  9. return {
  10. arr: ['a', 'b', 'c', 'd']
  11. }
  12. },
  13. mounted () {
  14. setTimeout(() => {
  15. this.arr.splice(1, 0, 'e')
  16. }, 1000);
  17. },
  18. }).mount('#app')
  19. </script>

典型的重排操作,使用patchChildren更新
image.png

设置了key的情况下,走patchKeyedChildren

  1. // ['a', 'b', 'c', 'd']
  2. // ['a', 'e', 'b', 'c', 'd']
  3. // 1.从开始同步:掐头
  4. // ['b', 'c', 'd']
  5. // ['e', 'b', 'c', 'd']
  6. // 2.从结尾同步:去尾
  7. // []
  8. // ['e']
  9. // 3.新增