• vue3学习源码笔记(小白入门系列)------ 重点!响应式原理 代码逐行分析


    备注

    本文中 只会涉及到 setup 主流程的 更新 , watch computed 等后面再分析
    带着问题,去源码寻找答案。
    响应式数据是什么时候创建的?
    什么时候进行的依赖收集?
    响应式数据更新后 怎么做的派发更新?

    本文中 用到的测试用例
    一定得 debug 跟着调试看 不然很容易绕晕

     it('should support runtime template compilation', async () => {
        const container = document.createElement('div')
        container.classList.add('app')
        const child = defineComponent({
          template: `
             

    {{age}}---{{status?add:'hihi'}}

    `
    , props:{ age:{ type: Number, default:20 } }, data(){ return { add: '12', status: true } }, mounted() { this.status = false this.add = '24' }, }) const App = { components:{child}, beforeMount() { console.log('beforeMount'); }, data() { return { } }, setup() { const count = ref(1) const age = ref('20') const obj = reactive({name:'ws',address:'usa'}) onMounted(()=>{ obj.name = 'kd' count.value = 5 age.value = '2' }) return ()=>{ return h('div',[obj.name,h(child,{age:age.value})]) } } } createApp(App).mount(container) await nextTick() expect(container.innerHTML).toBe(`0`) })
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    响应式数据创建

    还记得 之前文章中 初始化 setup 是在哪个阶段执行的吗?

    patch
    processComponent
    mountComponent
     // packages/runtime-dom/src/renderer.ts
      patch 阶段 组件首次挂载时
    // mountComponent 方法
     
    1. 先创建 组件 instance 实例
    2. 初始化 setup props 等属性
    3. 设置并运行带副作用的渲染函数
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    初始化 setup 时 ,就会创建响应式数据
    测试用例中 会先 执行 App 组件中 的setup 函数

     setup() {
            // 会创建一个 ref 响应式数据
            const count = ref(1)
            // 会创建一个 ref 响应式数据
            const age = ref('20')
            // 会创建一个 reactive 响应式数据
            const obj  = reactive({name:'ws',address:'usa'})
    
            onMounted(()=>{
              obj.name = 'kd'
              count.value = 5
              age.value = '2'
            })
    
     
            return ()=>{
              return  h('div',[obj.name,h(child,{age:age.value})])
            }
          }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    ref 和 reactive 核心 作用

    先说结论 :
    就是 数据 驱动 视图 更新的 桥梁 。依赖收集(getter) 和 派发更新(setter) 都在里面

    ref 和 reactive 差别不大(对于基本数据类型 proxy 无法做代理 ,所以vue3 自己利用 class 类中 get set 做的 代理工作 后续 依赖收集 和 派发更新 原理 和 reactive 基本一致 ) 下面 只对 reactive 做分析

    1. 先判断 代理对象 做类型分类
    function targetTypeMap(rawType) {
      switch (rawType) {
        case 'Object':
        case 'Array':
          return TargetType.COMMON
        case 'Map':
        case 'Set':
        case 'WeakMap':
        case 'WeakSet':
          return TargetType.COLLECTION
        default:
          return TargetType.INVALID
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 根据 不同分类 选择 不同的 getter setter 方法
      只分析下 最常见的 Object Array 代理
    export function reactive(target: object) {
      // if trying to observe a readonly proxy, return the readonly version.
      if (isReadonly(target)) {
        return target
      }
      return createReactiveObject(
        target,
        false,
        mutableHandlers,
        mutableCollectionHandlers,
        reactiveMap
      )
    }
    。。。。
    
    function createReactiveObject(
      target: Target,
      isReadonly: boolean,
      baseHandlers: ProxyHandler<any>,
      collectionHandlers: ProxyHandler<any>,
      proxyMap: WeakMap<Target, any>
    ) {
      // 省略。。。
      const proxy = new Proxy(
        target,
        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
      )
      proxyMap.set(target, proxy)
      return proxy
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    会走到 baseHandlers 也就是 mutableHandlers

    1. mutableHandlers 中 如何 做的 数据代理工作

    先看 get 核心 就是 依赖收集 track方法

    function createGetter(isReadonly = false, shallow = false) {
      return function get(target: Target, key: string | symbol, receiver: object) {
        // 对 ReactiveFlags 的处理部分
        if (key === ReactiveFlags.IS_REACTIVE) {
          return !isReadonly
        } else if (key === ReactiveFlags.IS_READONLY) {
          return isReadonly
        } else if (key === ReactiveFlags.IS_SHALLOW) {
          return shallow
        } else if (
          key === ReactiveFlags.RAW &&
          receiver ===
            (isReadonly
              ? shallow
                ? shallowReadonlyMap
                : readonlyMap
              : shallow
              ? shallowReactiveMap
              : reactiveMap
            ).get(target)
        ) {
          return target
        }
    
        const targetIsArray = isArray(target)
    
        if (!isReadonly) {
          // 数组的特殊方法处理
          if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
            return Reflect.get(arrayInstrumentations, key, receiver)
          }
          // 对象 hasOwnProperty 方法处理
          if (key === 'hasOwnProperty') {
            return hasOwnProperty
          }
        }
    
        // 取值
        const res = Reflect.get(target, key, receiver)
        // Symbol Key 不做依赖收集
        if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
          return res
        }
        // 进行依赖收集
        if (!isReadonly) {
          track(target, TrackOpTypes.GET, key)
        }
    
        // 一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露
        if (shallow) {
          return res
        }
    
        if (isRef(res)) {
          //跳过数组、整数 key 的展开
          // ref unwrapping - skip unwrap for Array + integer key.
          return targetIsArray && isIntegerKey(key) ? res : res.value
        }
    
        if (isObject(res)) {
          // Convert returned value into a proxy as well. we do the isObject check
          // here to avoid invalid value warning. Also need to lazy access readonly
          // and reactive here to avoid circular dependency.
          // 如果res 是 对象 且不是 readonly 就继续处理成 reactive
          return isReadonly ? readonly(res) : reactive(res)
        }
    
        return res
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    track 依赖收集

    export function track(target: object, type: TrackOpTypes, key: unknown) {
      if (shouldTrack && activeEffect) {
        let depsMap = targetMap.get(target)
        if (!depsMap) {
          targetMap.set(target, (depsMap = new Map()))
        }
        let dep = depsMap.get(key)
        if (!dep) {
          depsMap.set(key, (dep = createDep()))
        }
    
        const eventInfo = __DEV__
          ? { effect: activeEffect, target, type, key }
          : undefined
        // 将 activeEffect 存入到 dep 同时将 dep[] 存入到 activeEffect 中 deps 属性 上 
        trackEffects(dep, eventInfo)
      }
    }
    
    
    
    export function trackEffects(
      dep: Dep,
      debuggerEventExtraInfo?: DebuggerEventExtraInfo
    ) {
      let shouldTrack = false
      if (effectTrackDepth <= maxMarkerBits) {
      // 如果本轮副作用函数执行过程中已经访问并收集过,则不用再收集该依赖
        if (!newTracked(dep)) {
          dep.n |= trackOpBit // set newly tracked 标识本轮已经被收集过
          shouldTrack = !wasTracked(dep)
        }
      } else {
        // Full cleanup mode. 判断现在有没有activeEffect  有activeEffect才发生依赖收集
        // activeEffect 每个组件初始化的时候会有一个activeEffect 
        // 这一步的作用 是为了避免多余的依赖收集 例如在setup 创建了 响应式数据 同步 访问 或者 修改 这个数据 这时候 都不会发生 依赖收集。只会在 执行render函数的时候 才发生依赖收集 
        shouldTrack = !dep.has(activeEffect!)
      }
      
      if (shouldTrack) {
        dep.add(activeEffect!)
        activeEffect!.deps.push(dep)
      }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    reactiveEffect 核心代码

    // 用于记录位于响应上下文中的effect嵌套层次数
    let effectTrackDepth = 0
    // 二进制位,每一位用于标识当前effect嵌套层级的依赖收集的启用状态
    export left trackOpBit = 1
    // 表示最大标记的位数
    const maxMarkerBits = 30
    
    // 当前活跃的 effect
    let activeEffect;
    
    export class ReactiveEffect {
      // 用于标识副作用函数是否位于响应式上下文中被执行
      active = true
      // 副作用函数持有它所在的所有依赖集合的引用,用于从这些依赖集合删除自身
      deps = []
      // 指针为,用于嵌套 effect 执行后动态切换 activeEffect
      parent = undefined
      // ...
      run() {
        // 若当前 ReactiveEffect 对象脱离响应式上下文
        // 那么其对应的副作用函数被执行时不会再收集依赖
        if (!this.active) {
          return this.fn()
        }
        
        // 缓存是否需要收集依赖
        let lastShouldTrack = shouldTrack
        
        try {
          // 保存上一个 activeEffect 到当前的 parent 上
          this.parent = activeEffect
          // activeEffect 指向当前的 effect
          activeEffect = this
          // shouldTrack 置成 true
          shouldTrack = true
          // 左移操作符 << 将第一个操作数向左移动指定位数
          // 左边超出的位数将会被清除,右边将会补零。
          // trackOpBit 是基于 1 左移 effectTrackDepth 位
          trackOpBit = 1 << ++effectTrackDepth
          
          // 如果未超过最大嵌套层数,则执行 initDepMarkers
          if (effectTrackDepth <= maxMarkerBits) {
            initDepMarkers(this)
          } else {
            cleanupEffect(this)
          }
          // 这里执行了 fn
          return this.fn()
        } finally {
          if (effectTrackDepth <= maxMarkerBits) {
            // 用于对曾经跟踪过,但本次副作用函数执行时没有跟踪的依赖采取删除操作。
            // 新跟踪的 和 本轮跟踪过的都会被保留
            finalizeDepMarkers(this)
          }
          
          // << --effectTrackDepth 右移动 effectTrackDepth 位
          trackOpBit = 1 << --effectTrackDepth
          
          // 返回上个 activeEffect
          activeEffect = this.parent
          // 返回上个 shouldTrack
          shouldTrack = lastShouldTrack
          // 情况本次的 parent 指向
          this.parent = undefined
        }
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    在这里插入图片描述

    说明 :

    depsMap 中 effect[] 用于 每次 派发更新时候 去执行 effect 数组中的 reactiveEffect (实际调用 reactiveEffect 实例的 run 方法)

    reactiveEffect 中 会在执行 run 方法的时候 给 initDepMarkers 方法来 给 deps 数组中每个对象 添加 w 属性 表示 已经收集处理 在 依赖收集中 track----> trackEffects 会给 depsMap 中 dep(这个 dep 和 effect 实例的 deps 中 每一个对象 相对应) 赋值 n (表示 是 新收集的)

    export const finalizeDepMarkers = (effect: ReactiveEffect) => {
      const { deps } = effect
      if (deps.length) {
        let ptr = 0
        for (let i = 0; i < deps.length; i++) {
          const dep = deps[i]
          if (wasTracked(dep) && !newTracked(dep)) {
          // dep 类型是 set
            dep.delete(effect)
          } else {
            deps[ptr++] = dep
          }
          // clear bits
          dep.w &= ~trackOpBit
          dep.n &= ~trackOpBit
        }
        deps.length = ptr
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    当 effect.run() 中 注册的fn 函数执行完后 会调用 finalizeDepMarkers 去 删除掉 这一轮 dep 没有被收集到的 effect 避免 多余的 触发更新逻辑

    那测试用例来说明
    在这里插入图片描述

    第一轮的 依赖收集 发生时机

    在App 组件 初始化 副作用函数, 会先创建 reactiveEffect 并挂载到 app.instance
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    app 会主动触发 instance.update() 发生第一次 组件挂载 。
    之前章节 说明过 组件首次挂载流程
    实际调用的是 reactiveEffect.run ----> 执行componentUpdateFn —>render(生成subtree)----> patch ----> processElement

    在render 过程中 使用到的响应式数据 就发生依赖收集 。
    这时候 app 组件的 这一轮的依赖收集完成 使用到了 obj 和 age
    在这里插入图片描述
    这时候进行app 组件 processElement 由于存在子组件child 执行 mountChild ----> patch —> child 组件的processComponent child组件 也会和 app 组件 一样 去 初始化 instance 创建 ReactiveEffect 触发update 执行属于 child 的 componentUpdateFn 再 执行 child组件的 render 函数 child组件发生依赖收集

    在这里插入图片描述
    age status add

    本轮的依赖收集全部完成。

    总结:
    组件的首次依赖收集 发生在 render阶段 顺序是 父组件 setup---->父组件 render ---->子组件 setup 
    ----> 子组件render
    
    • 1
    • 2
    • 3

    setup 阶段 去更改了 响应式数据 会发生依赖收集吗

    例如:
    setup(){
       const age = ref(20)
       // 这里发生了访问操作
       const temp = age.value
       return ()=>{
          return h('div',[age.value]) 
       }
    }
    
    这时候会触发响应式数据的 get 操作 
    但是由于 没有 activeEffect(这时候 组件还没开始设置副作用函数(SetupRenderEffectFn)所以没有activeEffect) 所以不会发生依赖收集 
    
    扩展:
    setup(){
       const age = ref(20)
       setTimeout(()=>{
       // 这里发生了访问操作
          console.log(age.value);  
       })
       return ()=>{
          return h('div',[age.value]) 
       }
    }
    这时候 也会触发响应式数据的 get 操作 ,也是没有activeEffect(组件已经完成 effect.run 方法了,这时候 activeEffect 已经被置为空) 所以也不会发生依赖收集
    
    后续:
    在setup函数之后的生命周期(如mounted、updated等钩子函数)中访问响应式数据会触发依赖收集 (后面再分析)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    派发更新

    派发更新是什么时候 触发的?

    上面 组件 挂载完成后,我在mouted 生命周期钩子里面 写的修改响应式数据操作,会触发 setter 下面看看 reactive 的 setter 源码

    
    function createSetter(shallow = false) {
      return function set(
        target: object,
        key: string | symbol,
        value: unknown,
        receiver: object
      ): boolean {
        // 。。。 省略部分逻辑
        const hadKey =
          isArray(target) && isIntegerKey(key)
            ? Number(key) < target.length
            : hasOwn(target, key)
        const result = Reflect.set(target, key, value, receiver)
        // 如果target是原型链上的东西,不要触发
        if (target === toRaw(receiver)) {
          if (!hadKey) {
            // 新增操作
            trigger(target, TriggerOpTypes.ADD, key, value)
          } else if (hasChanged(value, oldValue)) {
            // 更新操作
            trigger(target, TriggerOpTypes.SET, key, value, oldValue)
          }
        }
        return result
      }
    }
    
    
    export function trigger(
      target: object,
      type: TriggerOpTypes,
      key?: unknown,
      newValue?: unknown,
      oldValue?: unknown,
      oldTarget?: Map<unknown, unknown> | Set<unknown>
    ) {
      // 根据 target 查到对应的 depsMap
      const depsMap = targetMap.get(target)
      // 不存在depsMap 不触发更新
      if (!depsMap) {
        // never been tracked
        return
      }
      
      // 用于 暂存 effect
      let deps: (Dep | undefined)[] = []
      if (type === TriggerOpTypes.CLEAR) {
        // collection being cleared
        // trigger all effects for target
        deps = [...depsMap.values()]
      } else if (key === 'length' && isArray(target)) {
        const newLength = Number(newValue)
        depsMap.forEach((dep, key) => {
          if (key === 'length' || key >= newLength) {
            deps.push(dep)
          }
        })
      } else {
        // schedule runs for SET | ADD | DELETE
        if (key !== void 0) {
          deps.push(depsMap.get(key))
        }
    
        // also run for iteration key on ADD | DELETE | Map.SET
        switch (type) {
          case TriggerOpTypes.ADD:
            if (!isArray(target)) {
              deps.push(depsMap.get(ITERATE_KEY))
              if (isMap(target)) {
                deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
              }
            } else if (isIntegerKey(key)) {
              // new index added to array -> length changes
              deps.push(depsMap.get('length'))
            }
            break
          case TriggerOpTypes.DELETE:
            if (!isArray(target)) {
              deps.push(depsMap.get(ITERATE_KEY))
              if (isMap(target)) {
                deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
              }
            }
            break
          case TriggerOpTypes.SET:
            if (isMap(target)) {
              deps.push(depsMap.get(ITERATE_KEY))
            }
            break
        }
      }
    
      const eventInfo = __DEV__
        ? { target, type, key, newValue, oldValue, oldTarget }
        : undefined
     
      // 最终处理 在这里
      if (deps.length === 1) {
        if (deps[0]) {
          if (__DEV__) {
            triggerEffects(deps[0], eventInfo)
          } else {
            triggerEffects(deps[0])
          }
        }
      } else {
        const effects: ReactiveEffect[] = []
        for (const dep of deps) {
          if (dep) {
            effects.push(...dep)
          }
        }
        // 下面操作 是为了 去重 保证相同的effect 只会有一个
        if (__DEV__) {
          triggerEffects(createDep(effects), eventInfo)
        } else {
          triggerEffects(createDep(effects))
        }
      }
    }
    
    
    export function triggerEffects(
      dep: Dep | ReactiveEffect[],
      debuggerEventExtraInfo?: DebuggerEventExtraInfo
    ) {
      // spread into array for stabilization
      const effects = isArray(dep) ? dep : [...dep]
      for (const effect of effects) {
        if (effect.computed) {
          triggerEffect(effect, debuggerEventExtraInfo)
        }
      }
      for (const effect of effects) {
        if (!effect.computed) {
          triggerEffect(effect, debuggerEventExtraInfo)
        }
      }
    }
    
    function triggerEffect(
      effect: ReactiveEffect,
      debuggerEventExtraInfo?: DebuggerEventExtraInfo
    ) {
      if (effect !== activeEffect || effect.allowRecurse) {
        if (__DEV__ && effect.onTrigger) {
          effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
        }
        // 最终会执行 scheduler 是在 初始化的时候 创建的
        if (effect.scheduler) {
          effect.scheduler()
        } else {
          effect.run()
        }
      }
    }
    
     //  在SetupRenderEffectFn 阶段中 create reactive effect for rendering
        const effect = (instance.effect = new ReactiveEffect(
          componentUpdateFn,
          () => queueJob(update),// 这个就是 scheduler
          instance.scope // track it in component's effect scope
        ))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165

    总结:当响应式数据被更新 且 对应的 depsMap 不为空 就会触发 组件更新(如何更新 则下个问题给出答案)

    扩展: setup阶段 响应式数据被修改 会触发组件更新吗
    setup(){
       const age = ref(20)
       // 修改操作
       age.value = 10
       return ()=>{
          return h('div',[age.value]) 
       }
    }
    会触发 setter 操作 由于 depsMap 为空 所以不会发生派发更新
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    vue 是如何根据派发更新来触发组件的更新渲染的?

    在这里插入图片描述
    派发更新的核心就是 触发 effect.scheduler(常规的组件写法 就是 会给activeEffect 创建 scheduler)

    const effect = (instance.effect = new ReactiveEffect(
          componentUpdateFn,
          () => queueJob(update), // effect.schedule
          instance.scope // track it in component's effect scope
        ))
    
    • 1
    • 2
    • 3
    • 4
    • 5

    分析下 queueJob

    
    export function queueJob(job: SchedulerJob) {
      // the dedupe search uses the startIndex argument of Array.includes() 确保不会重复设置 schedule
      // by default the search index includes the current job that is being run 默认包括正在运行的 schedule
      // so it cannot recursively trigger itself again. 避免递归触发自身再次运行
      // if the job is a watch() callback, the search will start with a +1 index to 运行在watch 中 重复运行
      // allow it recursively trigger itself - it is the user's responsibility to
      // 确保它不会陷入无限循环
    
      // 去重判断
      if (
        !queue.length ||
        !queue.includes(
          job,
          isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
        )
      ) {
      //添加到队列尾部
        if (job.id == null) {
          queue.push(job)
        } else {
          // 按照 job id 自增的顺序添加 (一般父组件的id 要小于子组件 保证 父组件永远先于子组件触发更新)
          // 这个id 是由 instance.uid 决定 就是在初始化组件实例 确定( 具体代码 runtime-core/src/component) 先初始化的 uid(每次创建组件实例 全局 uid会加1) 会小,
          queue.splice(findInsertionIndex(job.id), 0, job)
        }
        queueFlush()
      }
    }
    
    // 通过promise.then 创建 微任务(去执行flushjob)
    function queueFlush() {
      if (!isFlushing && !isFlushPending) {
        isFlushPending = true
        currentFlushPromise = resolvedPromise.then(flushJobs)
      }
    }
    
    
    function flushJobs(seen?: CountMap) {
      // 是否正在等待执行
      isFlushPending = false
      // 正在执行
      isFlushing = true
    
    
       // 在更新前,重新排序好更新队列 queue 的顺序
      // 这确保了:
      // 1. 组件都是从父组件向子组件进行更新的。(因为父组件都在子组件之前创建的
      // 所以子组件的渲染的 effect 的优先级比较低)
      // 2. 如果父组件在更新前卸载了组件,这次更新将会被跳过。
      queue.sort(comparator)
    
     
    
      try {
      // 遍历主任务队列,批量执行更新任务
        for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
          const job = queue[flushIndex]
          if (job && job.active !== false) {
            if (__DEV__ && check(job)) {
              continue
            }
            // 这个 job 就是 effect.run
            callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
          }
        }
      } finally {
       // 队列任务执行完,重置队列索引
        flushIndex = 0
         // 清空队列
        queue.length = 0
        // 执行后置队列任务
        flushPostFlushCbs(seen)
        // 重置队列执行状态
        isFlushing = false
        // 重置当前微任务为 Null
        currentFlushPromise = null
        // 如果主任务队列、后置任务队列还有没被清空,就继续递归执行
        if (queue.length || pendingPostFlushCbs.length) {
          flushJobs(seen)
        }
      }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    总结:在组件mounted 触发的派发更新 会被收集到 一个微任务执行任务队列中 ,在主流程宏任务 执行完后 就会 去执行 微任务任务队列 开始 触发 执行 job (effect.run — > updateComponentFn)

    组件副作用函数执行时 有多个响应式数据更新 是如何保证组件只会触发一次更新渲染的?

    有了 上面源码的分析 我们已经可以得出答案 为啥 在一个组件的 副作用函数执行时 多个响应式数据更新 只会触发一次 组件更新
    演示代码:

    setup(){
       const num1 = ref(20)
       
       const num2 = ref(10)
    
       onMounted(()=>{
         num1.value = 40
         num1.value = 50
         num2.value = 100
       })
       
    
       return ()=>{
          return h('div',[num1.value+num2.value]) 
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在 onMounted 中 更新了三次 触发三次 triggerEffect 会有三次 往微任务 放入 update 操作,由于 传入的 job.id 都是同一个 所以 在更新队列中 只会被创建一个更新任务 组件也只会被更新一次

    多余的组件依赖 是如何被清理掉的?

    组件 再每次 渲染后 会 去 清理 后续没被收集的 effect (对应的是 每个响应式数据 对应的dep(set) 中的 reactiveEffect )

    例子:

    const child = defineComponent({
          template: `
             

    {{age}}---{{status?add:'hihi'}}

    `
    , props:{ age:{ type: Number, default:20 } }, data(){ return { add: '12', status: true } }, mounted() { this.status = false this.add = '24' }, }) // mounted 阶段 改变了 status 触发了 组件更新 重新 render 的 时候 会发生新的一轮依赖收集 // 之前 组件 是有两个 dep 一个 属于 status 一个属于 add 但是,由于新的依赖收集 add 不会被用到 所以 在 effect.run 执行完 后 add 的 dep 会被清除掉 是根据 dep 赋值的 w 和 n 属性 去比较
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    8位二进制cpu的设计和制作
    学习笔记——IP地址网络协议——网络层(IP)协议
    简单介绍Rope Crystal(类似Roop)项目
    es6中的let与const关键字及其与var关键字的不同
    【华为上机真题 2022】字符串最后一个单词的长度
    事件修饰符
    模拟面试
    【Docker】学习笔记(一)
    Swagger enum 最佳实践:深度剖析与应用指南
    3分钟,快速上手Postman接口测试!
  • 原文地址:https://blog.csdn.net/weixin_45485922/article/details/132754955