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

目录

  • 前言
    • 实现
      • 核心 ComputedRefImpl 的实现原理
        • step1:
        • step2:
        • step3:
        • step4:
        • step5:
    • 总结:

前言

带着问题看源码:
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 

核心 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)
  }
}

结合代码来分析 可以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])
        }
      }
    }
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
  }
 

先进行依赖收集(收集的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岁'

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)
      }
    })

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

总结:

  1. 计算属性 也是内置 实现了 一个 reactiveEffect 来实现响应式
  2. 计算属性可以从状态数据中计算出新数据,computed 和 methods 的最大差异是它具备缓存性,如果依赖项不变时不会重新计算,而是直接返回缓存的值,是否重新计算 取决于私有属性 _dirty

你可能感兴趣的:(vue3源码学习,学习,笔记)