【Vue3源码分析】【流程分析】

简单例子

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' }
]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值