Vue3源码学习-响应式原理-ref

82 阅读1分钟

案例

<script src="../../dist/vue.global.js"></script>

<div id="demo">
  <h1>counts:{{ counts }}</h1>
  <button @click="changeCounts">更改counts</button>
</div>

<script>
  const { createApp, ref } = Vue;

  createApp({
    setup() {
      const counts = ref(0)
      const changeCounts = () => {
        counts.value++
      }
      return {
        counts,
        changeCounts
      }
    },
  }).mount('#demo')

</script>

调用ref函数发生了什么?返回了什么?

export function ref(value?: unknown) {
  return createRef(value, false)
}

export function isRef(r: any): r is Ref {
  return !!(r && r.__v_isRef === true)
}

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

从上面👆代码我们可以看出来,当我们执行const counts = ref(0)时,ref(0)其本质就是返回了一个RefImpl的构造函数

createRef中,会先去判断传入的rawValue是否已经是一个ref类型的值,如果是的话就不需要再进行包装了

class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(
    value: T,
    public readonly __v_isShallow: boolean,
  ) {
    // 存储原始值
    this._rawValue = __v_isShallow ? value : toRaw(value)
    // 存储被ref包装的值
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    // 收集依赖
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    // 如果是浅代理,或者传入的值是浅代理,或者传入的值是只读的,那么直接使用传入的值
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      // 触发依赖
      triggerRefValue(this, DirtyLevels.Dirty, newVal)
    }
  }
}
  • 当我们读取counts的值时,会触发get函数,开始收集依赖并将值返回
  • 当我们更改counts的值时,会触发set函数,会判断是否浅代理,或者传入的值是浅代理的,或者传入的值是只读的,那么直接用传入的值,否则的话就找出最原始的值来,也就是某一个值包装成响应式对象后,我取出一开始进行包装的值也就是原始值
  • 使用hasChanged判断传入的值和原始值是否不一致,不一致则进行更改,并触发依赖