有关 vue 3 ref 的实现问题

62 阅读2分钟

前言

首先 ref 的出现是为了解决原始值的响应式问题

使用

<script setup> 
    const refData = ref(1)
    refData.value = 2 // 触发响应式更新
    console.log(refData.value) // 2
</script>
<template>
    <div>
        {{ refData }}
    </div>
</template>

这里发现有些不同的地方,我们在模板中使用不加 value, 而在 js 里面需要带上 value,为了搞清楚这里面的关系,我们去源码里一探究竟

具体实现

具体的实现在源码里 packages\reactivity\src\ref.ts

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

function createRef(rawValue: unknown, shallow: boolean) {

  // 当使用 ref(1) 时,此时 isRef 还是 false;
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

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

  // 这个是收集 effect 函数的 Set
  public dep?: Dep = undefined
  // 这里 把 __v_isRef 设置 为 true,表示这时一个 ref 数据,用以和 reactive 数据作区分
  public readonly __v_isRef = true

  constructor(value: T, public readonly __v_isShallow: boolean) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    
    // 这里会走到 toReactive(value),也就是调用 reactive(value), 
    // 这个主要针对的是如果传入的是对象的情况
    this._value = __v_isShallow ? value : toReactive(value)
  }
    
    // 对于 原始值,
  get value() {
    // 当调用 refData.value 会执行这里,
    trackRefValue(this) // 收集当前对象到 dep 中去
    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, newVal  // 这里触发更新,当 执行refData.value = newVal 时更新 effect。
    }
  }
}

上面解决了为什么在 js 中调用 ref 需要加 value 的问题,下面需要解决模板中不用 value的问题


export function unref<T>(ref: T | Ref<T>): T {
  return isRef(ref) ? (ref.value as any) : ref
}

const shallowUnwrapHandlers: ProxyHandler<any> = {
  // 这里会根据我们在创建 ref 时定义的 __v_isRef = true 来进行脱 value 处理
  // 如果是 ref 数据,那么访问 refData 直接返回 refData.value;
  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  set: (target, key, value, receiver) => {
    const oldValue = target[key]
    // 这里 设置时也一样 如果是 ref 数据, refData = nVal 相当于 refData.value = nVal
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    } else {
      return Reflect.set(target, key, value, receiver)
    }
  }
}

export function proxyRefs<T extends object>(
  objectWithRefs: T
): ShallowUnwrapRef<T> {
  return isReactive(objectWithRefs)
    ? objectWithRefs
    : new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

上面是拦截 ref 数据的 gettersetter 操作,在处理 setup数据时会调用 proxyRefs 数据

instance.setupState = proxyRefs(setupResult)

最终我们理解了 ref 的实现

总结

  • ref 主要是解决原始值的响应式问题
  • ref 的实现就是把传入 ref 的数据用一个对象包起来,并且记录下数据是否是ref数据,然后调用 reactive,就类似下面这样
function ref(val) {
    const wrapper = {
        value: val
    }
    Object.defineProperty(wrapper, 'isRef', { value: true })
    return reactive(wrapper)
}
  • 模板中使用不用加 value 是因为调用了proxyRefs脱了value

有一些细节是猜的,但是大概应该没问题,过路大佬如果有问题多多指教,谢谢!