• vue3学习源码笔记(小白入门系列)------computed是如何工作的


    前言

    带着问题看源码:
    1.computed 是如何实现响应式的?
    2.computed 是如何实现计算结果缓存的?

    实现

    
    function computed(getterOrOptions, debugOptions, isSSR = false) {
      let getter
      let setter
      // 判断第一个参数是不是一个函数
      const onlyGetter = isFunction(getterOrOptions)
      
      // 构造 setter 和 getter 函数
      if (onlyGetter) {
        getter = getterOrOptions
        // 如果第一个参数是一个函数,那么就是只读的
        setter = __DEV__
          ? () => {
              console.warn('Write operation failed: computed value is readonly')
            }
          : NOOP
      } else {
        getter = getterOrOptions.get
        setter = getterOrOptions.set
      }
      // 构造 ref 响应式对象
      const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
      // 返回响应式 ref
      return cRef
    }
    
    
    入参
    getterOrOptions:ComputedGetter<T> | WritableComputedOptions<T>
    export interface WritableComputedOptions<T> {
      get: ComputedGetter<T>
      set: ComputedSetter<T>
    }
    export type ComputedGetter<T> = (...args: any[]) => T
    
    
    返回值
    ComputedRef 继承于 WritableComputedRef 继承于 Ref 
    
    • 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

    核心 ComputedRefImpl 的实现原理

    会根据 传入的参数 将判断是否onlyGetter 我们只分析 isSSR为false 的 情况

    class ComputedRefImpl {
      public dep = undefined
    
      private _value
      public readonly effect
      //表示 ref 类型
      public readonly __v_isRef = true
      //是否只读
      public readonly [ReactiveFlags.IS_READONLY] = false
      //用于控制是否进行值更新(代表是否脏值)
      public _dirty = true
      // 缓存
      public _cacheable
    
      constructor(
        getter,
        _setter,
        isReadonly,
        isSSR
      ) {
        // 把 getter 作为响应式依赖函数 fn 参数
        this.effect = new ReactiveEffect(getter, () => {
          if (!this._dirty) {
            this._dirty = true
            // 触发更新
            triggerRefValue(this)
          }
        })
        // 标记 effect 的 computed 属性
        this.effect.computed = this
        this.effect.active = this._cacheable = !isSSR
        this[ReactiveFlags.IS_READONLY] = isReadonly
      }
    
      get value() {
       
        const self = toRaw(this)
        // 依赖收集 
        trackRefValue(self)
        // 脏值则进行更新
        if (self._dirty || !self._cacheable) {
        //step2 
          self._dirty = false
          // 更新值
          self._value = self.effect.run()!
        }
        //step4 
        return self._value
      }
      // 执行 setter
      set value(newValue) {
        this._setter(newValue)
      }
    }
    
    
    • 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

    结合代码来分析 可以debug 验证

    例子

    const App = {
    
          setup() {
            const age = ref('20')
           
    
            onMounted(()=>{
            // step5
               age.value = '2'
            })
    
            // step1
            const info = computed(()=>{
            // step3
              return age.value + '岁'
            })
    
            obj.name = 'ws'
            age.value = '29'
    
            
            return ()=>{
              return  h('div',[info.value+info.vlaue])
            }
          }
        }
    
    • 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
    step1:

    代码执行到 mark-one 会去执行 computed —> 初始化 ComputedRefImpl 得到一个 Ref 的 ComputedRef 子类

    step2:

    当 App 组件 挂载 执行 render 函数的时候 会访问 到 info.value。这个时候 会走到 ComputedRefImpl get value 方法里。此时 _dirty 为 true 会执行

       get value() {
        const self = toRaw(this)
        // 依赖收集 
        trackRefValue(self)
        // 脏值则进行更新
        if (self._dirty || !self._cacheable) {
          self._dirty = false
          // 更新值 
          self._value = self.effect.run()!
        }
        return self._value
      }
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    先进行依赖收集(收集的target 是 info(ref对象),对应的effect 是 组件实例的effect 副作用函数就是 componentUpdateFn),在执行 ComputedRefImpl 实例上的 effect.run -----> 也就是 getter ,传入 computed 的函数

    step3:

    执行 传入的 computed 的函数

    const info = computed(()=>{
       return age.value + '岁'
    })
    
    这时候访问 age.value 会进行依赖收集(收集的target 是 age 对应的effect是 ComputedRefImpl 实例上的 effect 后面简称 ComputedEffect )。
    返回 执行的 结果 info.value 此时为 '20岁'
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    step4:

    render 函数 访问 第二个 info.value 时
    _dirty 此时已经是 false, 会直接 return self._value 不会 再去计算

    step5:

    render 完后 执行 onmounted 钩子 修改 age.value 派发更新 上面step3 依赖收集的effect 是 computedEffect 会执行 effect.schdule

     this.effect = new ReactiveEffect(getter, () => {
     // 执行这里的方法
          if (!this._dirty) {
            this._dirty = true
            // 触发更新
            triggerRefValue(this)
          }
        })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    先将 _dirty 设置为 true 再触发 之前 step2 收集的 组件effect 更新 会重新 执行 render 又会访问到 info.value —> 再到 get 中 由于 _dirty 已经被设置为 true
    会执行 依赖收集(重复的job 会自动过滤掉) 执行 重新计算 effect.run() ---->getter
    获得最新的值

    总结:

    1. 计算属性 也是内置 实现了 一个 reactiveEffect 来实现响应式
    2. 计算属性可以从状态数据中计算出新数据,computed 和 methods 的最大差异是它具备缓存性,如果依赖项不变时不会重新计算,而是直接返回缓存的值,是否重新计算 取决于私有属性 _dirty
  • 相关阅读:
    3D 沙盒游戏之避障踩坑和实现之旅
    MATLAB程序设计与应用 4.3 函数文件
    Java程序设计——Swing UI 高级组件(三)
    git使用大全
    Hive之Map常用方法
    压缩文件7-Zip与WinRAR个人免费版在不同压缩等级下的对比
    《C++ Primer》第9章 顺序容器(二)
    [力扣] 剑指 Offer 第二天 - 从尾到头打印链表
    C-内存函数(大量图解,函数实现)
    使用快解析搭建自己的minecraft服务器
  • 原文地址:https://blog.csdn.net/weixin_45485922/article/details/133644649