首页 > 编程语言 >Vue3源码阅读梳理

Vue3源码阅读梳理

时间:2022-12-28 15:01:40浏览次数:47  
标签:遍历 name 梳理 源码 key Vue3 节点 patch children

简单代码例子

const { createApp, defineComponent, computed, watch, ref, reactive, effect } =
  Vue
const app = createApp({
  components: [],
  template: `
<div ref="testRef">
  <div
    key="i"
    v-for="(item111, index222, haha333) in list"
    @click="list.push({id: list.length, name: 'll'})"
  >
    <div>first</div>
    <li>second</li>
    <span>third</span>
    <span>third</span>
    {{m2}}
  </div>
  <button @click="show = !show">switch message</button>
  <div v-if="show">3333 {{message}}</div>
</div>
`,
  mounted() {
    console.log('mounted')
  },
  beforeDestroy() {
    console.log('beforeDestroy')
  },
  data() {
    return {
      message: 'Hello Vue!',
      eventName: 'click',
      list: [
        { id: 1, name: 'nzz' },
        { id: 2, name: 'ryy' },
        { id: 3, name: 'lhc' }
      ],
      show: false
    }
  },
  methods: {
    handleCLick() {
      console.log('handleCLick')
    }
  },
  computed: {
    m2() {
      return this.message + '33'
    }
  }
}).mount('#app')

上述 例子 简要的运行流程

第一次渲染

  • createApp(...).mount('#app')

    1. createVNode(...) 创建 vnode

      • guardReactiveProps 若 props 是响应式对象,则拷贝其数据再操作
      • normalizeClass normalizeStyle 对 class 和 style 的对象和数组模式进行处理
      • 根据 type 创建 shapeFlag
      • normalizeChildren
        • shapeFlag 再处理
    2. render(...) 根据 vnode 创建元素并挂载到指定位置。绑定数据和视图的关系。

      • patch vnode 到指定根元素(这里的 vnode 是组件 vnode)。
        • mountComponent 挂载组件。
          • setupComponent 执行时用 reactive 创建响应式数据
            • setupStatefulComponent 设置有状态的组件
              • finishComponentSetup 完成组件设置
                • compileToFunction 将模板解析成函数
                  • compile
                    1. baseParse 解析 template,生成 ast。
                    2. transform 转换优化数据。
                    3. generate 生成渲染函数 (是字符串形式)。
                  • 使用 new Function 将渲染函数字符串变成函数。
                • applyOptions
                  • reactive 创建响应式对象
          • setupRenderEffect 设置渲染 effect,并挂载组件。
            1. 创建 componentUpdateFn。用 componentUpdateFn 创建 reactiveEffect。
            2. 调用 reactiveEffect 的 run 方法。这会去执行 componentUpdateFn。
              componentUpdateFn 会 patch 标签或组件到指定位置。patch 之前以及之后会调用生命周期函数。
              patch 过程中会访问到响应式数据的属性值,会收集上述 reactiveEffect 到 targetMap 中对应响应式数据所映射的 Map 的对应属性所映射的 Dep 中。

点击按钮,改变响应式对象属性值引发重新渲染

  • 点击按钮 响应式对象属性值改变
    • setter 监听到属性值改变,调用 trigger 方法
      • 找到 targetMap 中对应对象的对应属性的对应 Dep,执行所有的 reactiveEffect。
        • 执行时会调用 componentUpdateFn。
          componentUpdateFn 会 patch 组件。

diff 算法

patchUnkeyedChildren

同时遍历各新旧 children,patch 相同索引位置的节点。
若新 children 多出节点,挂载。
若新 children 少了的节点,就卸载。

patchKeyedChildren

  1. sync from start
    从头往后遍历,若是 SameVNodeType,那么 patch 这俩节点。
    若不是,则退出遍历。
    (a b) c
    (a b) d e

  2. sync from end
    从后往前遍历,若是 SameVNodeType,那么 patch 这俩节点。
    若不是,则退出遍历。
    a (b c)
    d e (b c)

  3. common sequence + mount
    若遍历的位置超出旧 children,但是在新 children 范围内。
    这说明新 children 比就节点多了一些节点。
    需要挂载这些多出来的节点。
    (a b)
    (a b) c
    i = 2, e1 = 1, e2 = 2
    (a b)
    c (a b)
    i = 0, e1 = -1, e2 = 0

  4. common sequence + unmount
    若遍历的位置在旧 children 范围内,但是超出新 children。
    这说明新 children 比旧 children 少了一些节点。
    需要卸载这些多出来的节点。
    (a b) c
    (a b)
    i = 2, e1 = 2, e2 = 1
    a (b c)
    (b c)
    i = 0, e1 = 0, e2 = -1

  5. unknown sequence
    若非上述的集中情况,那么就不是节点边缘发生了改变,而是节点中间有变化。
    [i ... e1 + 1]: a b [c d e] f g
    [i ... e2 + 1]: a b [e d c h] f g
    i = 2, e1 = 4, e2 = 5

5.1 build key:index map for newChildren
将新 children 的 key 和 index 关系保存到 keyToNewIndexMap 中。

5.2 loop through old children left to be patched and try to patch
matching nodes & remove nodes that are no longer present
遍历旧 children,移除在新 children 中不再出现的节点。
判断该节点是否出现?
若有 key,则看是否有 key 匹配;
若无 key,则看是否 isSameVNodeType(key 和 type 都相等)。
若在新 children 中再次出现,
则 patch 之。

5.3 move and mount
generate longest stable subsequence only when nodes have moved
找到相对顺序不变的最大的子序列。
移动非这个子序列中的元素,到正确的位置。

比较 patchUnkeyedChildren 和 patchKeyedChildren

某节点有大量子孙节点,且该节点的位置经过移动。
此时,若节点有 key,会比无 key,更新时所消耗的资源要小。
无 key 的情况,需要卸载大量子孙节点,再在指定位置处,再大量创建子孙节点。
有 key 的情况,则移动该节点即可。少去了卸载和创建的操作。

那是否有某种情况,无 key 会比有 key 更优呢?
查找不用移动的最长子序列调用的方法 getSequence,貌似需要消耗一些资源。
若,children 是大量普通 div,只有一层,内容是字符串。然后打乱顺序。
这种情况下,无 key 应该会比有 key 更优吧?
如何测试看实际效果呢?

const unkeyedl1 = [
  { name: '1' },
  { name: '2', children: [{ name: '21' }, { name: '22' }] },
  { name: '3' },
  { name: '4' },
  { name: '5' }
]
const unkeyedl2 = [
  { name: '1' },
  { name: '3333' },
  { name: '4444' },
  { name: '2222', children: [{ name: '222221' }, { name: '222222' }] },
  { name: '5' }
]
const keyedl1 = [
  { key: 1, name: '1' },
  {
    key: 'b',
    name: '2',
    children: [
      { key: 'b1', name: '21' },
      { key: 'b2', name: '22' }
    ]
  },
  { key: 'c', name: '3' },
  { key: 'd', name: '4' },
  { key: 5, name: '5' }
]
const keyedl2 = [
  { key: 1, name: '1' },
  { key: 'c', name: '3333' },
  { key: 'd', name: '4444' },
  {
    key: 'b',
    name: '2222',
    children: [
      { key: 'b1', name: '222221' },
      { key: 'b2', name: '222222' }
    ]
  },
  { key: 5, name: '5' }
]

标签:遍历,name,梳理,源码,key,Vue3,节点,patch,children
From: https://www.cnblogs.com/toLivetoLearn/p/17010145.html

相关文章

  • Vue3+vant+ts 上滑加载,解决上滑调用多次数据的问题
    之前用vue2的时候,写过vue2的用法,链接在这里点击跳转哈,用得挺好的,也没啥问题,照葫芦画瓢的做出来了,但是有问题,下滑之后调用多次数据,按理说组件通过 loading 和 finished......
  • 修改内核源码绕过反调试检测(Android10)
    一、Android反调试     反调试在代码保护中扮演着非常重要的角色,虽然不能完全阻止攻击者,但是能加大攻击者的分析时间成本。目前绝大多数Androidapp都加固了,为了防......
  • 解密随机数生成器(二)——从java源码看线性同余算法
    RandomJava中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linearcongruentialformula线性同余方程(DonaldKnuth的编程艺术的3.2.1节)如果两个Random实例使......
  • Python爬虫实战,requests+openpyxl模块,爬取小说数据并保存txt文档(附源码)
    前言今天给大家介绍的是Python爬取小说数据并保存txt文档,在这里给需要的小伙伴们代码,并且给出一点小心得。首先是爬取之前应该尽可能伪装成浏览器而不被识别出来是爬虫,基......
  • 沐雪多租宝商城源码从.NetCore3.1升级到.Net6的步骤
    .Net6是继.NetCore3.1之后的有一LTS长期支持版本,我们一般会将.NetCore3.1项目直接升级到.Net6,网上有很多人说,需要先从.NetCore3.1升级到.Net5,再升级到.Net6,其实我觉得直接......
  • Vue3 Composition API 的优势
     1.OptionsAPI存在的问题使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改。 2.CompositionAPI的优势我们可以更加优雅的组织......
  • Vue3之provide 与 inject
     provide与inject 作用:实现祖与后代组件间通信,儿子组件中也能用这种方式,但是一般不这么用,父子组件传信息一般直接用props属性。套路:父组件有一个 provide 选项......
  • Vue3之响应式数据的判断
    响应式数据的判断isRef:检查一个值是否为一个ref对象isReactive:检查一个对象是否是由 reactive 创建的响应式代理isReadonly:检查一个对象是否是由 readonly......
  • Vue3之customRef
    customRef作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制。比如在input更新数据之后,设置指定时间之后再在h3标签上重新展示最新的数据:<templ......
  • 初一数学梳理卷
    初一诊断卷一、因式分解\(6x^2-13x+6\)\(原式=(2x-3)(3x-2)\)\(16mx(a-b)^2-9mx(a+b)^2\)\(原式=mx[16(a-b)^2-9(a+b)^2]\)\(=mx[(4a-4b)^2-(3a+3b)^2]\)\(=mx[......