likes
comments
collection
share

Vue3源码系列 (四) ref

作者站长头像
站长
· 阅读数 15

我们知道,一般用reactive来定义一个响应式对象,ref常用来定义一个响应式的原始值。上篇文章已经聊过了reactive,知晓了如何通过Proxy来对目标对象进行代理从而实现响应式,而非对象的这些原始值的响应式问题就交给ref来解决了。

一、refshallowRef的函数签名

refshallowRef各有三种重载,入参各不相同,都返回一个Ref/ShallowRef类型的值。通过createRef函数创建一个响应式的值。和 reactive 相似,reactive也是通过调用createReactiveObject来创建一个响应式的对象。而createRef创建并返回一个 RefImpl 实例。

// ref
export function ref<T extends object>(
  value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
  return createRef(value, false)
}
​
// shallowRef
export function shallowRef<T extends object>(
  value: T
): T extends Ref ? T : ShallowRef<T>
export function shallowRef<T>(value: T): ShallowRef<T>
export function shallowRef<T = any>(): ShallowRef<T | undefined>
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}
​
// ...// createRef
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

二、RefImpl

顺带一说,在ts里,关键字class既声明了一个值,也声明了一个ts类型。RefImpl算是ref的核心内容了,构造器函数接收两个参数,value是传入的原本的值,__v_isShallow在上一篇讲reeactivereadonly的文章里,也有这个属性,用于区别深层/浅层。且isShallow()函数也会利用这个属性来做判断。在这里两个作用,一是作为实例属性供isShallow判断;而是根据传入时来值来判断是否是浅层的Ref,因为函数shallowRef也是创建一个RefImpl实例。

可以看到,Ref的响应式实现就比较简单了,用_value属性来存储实际的值,用dep属性存储依赖,用在classgetter里通过trackRefValue(this)来收集依赖,在setter里调用triggerRefValue(this, newVal)。和vue2里的实现相似,只是这里使用的 classgettersetter,而vue2里使用的是属性描述符里的gettersetter

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)
    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)
    // shallowRef或者 新值 是浅层的或者只读的,则设置值的之前对新值解包
    newVal = useDirectValue ? newVal : toRaw(newVal)
    // 对比新值和旧值,如果有改变则触发更新
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

三、trackRefValue

trackRefValue用于收集Ref的依赖,接收一个RefBase类型的值。在ref函数中则是接收RefImpl的实例。shouldTrack是从effect的模块引入的,用做暂停和恢复捕获依赖的标志;activeEffect也是从effect的模块引入,标记当前活跃的effect。可以看到,内部调用trackEffects函数来收集依赖,该函数来自effect的模块,放在effect的篇章里讲。

// trackRefValue
export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    // 对Ref进行解包
    ref = toRaw(ref)
    if (__DEV__) {
      trackEffects(ref.dep || (ref.dep = createDep()), {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      trackEffects(ref.dep || (ref.dep = createDep()))
    }
  }
}

四、triggerRefValue

triggerRefValue函数用于触发Ref的响应式更新。triggerEffects函数来自effect的模块,在effect的篇章里讲到。

export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  // 对Ref进行解包
  ref = toRaw(ref)
  // 当有收集到依赖时,触发更新
  if (ref.dep) {
    if (__DEV__) {
      triggerEffects(ref.dep, {
        target: ref,
        type: TriggerOpTypes.SET,
        key: 'value',
        newValue: newVal
      })
    } else {
      triggerEffects(ref.dep)
    }
  }
}

五、customRefCustomRefImpl

Vue3还提供了自定义的Ref,自己传入gettersetter,可以自由选择tracktrigger的时机。

class CustomRefImpl<T> {
  public dep?: Dep = undefined
​
  private readonly _get: ReturnType<CustomRefFactory<T>>['get']
  private readonly _set: ReturnType<CustomRefFactory<T>>['set']
​
  public readonly __v_isRef = true
​
  constructor(factory: CustomRefFactory<T>) {
    const { get, set } = factory(
      () => trackRefValue(this),
      () => triggerRefValue(this)
    )
    this._get = get
    this._set = set
  }
​
  get value() {
    return this._get()
  }
​
  set value(newVal) {
    this._set(newVal)
  }
}
​
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
  return new CustomRefImpl(factory) as any
}

六、toReftoRefsObjectRefImpl

setup函数中返参时,我们有时候想要对响应式对象的某个属性进行解构,往往是用到toRef来创建一个ObjectRefImpl实例。

可以看到,原来的响应式对象依然被这个ObjectRefImpl实例通过_object属性引用。而在getter里面,会通过原本的响应式对象_object来访问该值,因而依赖的收集是由原本的响应式对象_object来进行的;同样的,在setter里,也是通过引用原本的响应式对象_object来达到赋值的操作,从而在_object中触发更新。也就是说,ObjectRefImpl不负责依赖收集和响应式更新,这些都是借由原本的响应式对象_object完成的。

toRef简要判断入参是否是一个Ref,是则直接返回,否则返回一个新建的ObjectRefImpl

class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true
​
  constructor(
    // 私有只读属性 原本的响应式对象
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K]
  ) {}
​
  get value() {
    const val = this._object[this._key]
    return val === undefined ? (this._defaultValue as T[K]) : val
  }
​
  set value(newVal) {
    this._object[this._key] = newVal
  }
}
​
// ts类型ToRef<T>
export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>
​
// 重载                             
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K
): ToRef<T[K]>
​
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue: T[K]
): ToRef<Exclude<T[K], undefined>>
​
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue?: T[K]
): ToRef<T[K]> {
  const val = object[key]
  return isRef(val)
    ? val
    : (new ObjectRefImpl(object, key, defaultValue) as any)
}

toRefs则是对传入的对象/数组进行遍历并进行toRef解构。

export function toRefs<T extends object>(object: T): ToRefs<T> {
  if (__DEV__ && !isProxy(object)) {
    console.warn(`toRefs() expects a reactive object but received a plain one.`)
  }
  const ret: any = isArray(object) ? new Array(object.length) : {}
  for (const key in object) {
    ret[key] = toRef(object, key)
  }
  return ret
}