关于vue3:vue3源码九ref源码解析

6次阅读

共计 3980 个字符,预计需要花费 10 分钟才能阅读完成。

【vue3 源码】九、ref 源码解析

参考代码版本:vue 3.2.37

官网文档:https://vuejs.org/

ref承受一个外部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其外部值的 property.value

应用

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

源码解析

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

ref返回 createRef 函数的返回值。

createRef接管两个参数:rawValue待转换的值、shallow浅层响应式。

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

如果 rawValue 本就是 ref 类型的会立刻返回 rawValue,否则返回一个RefImpl 实例。

RefImpl

class RefImpl<T> {
  private _value: T
  private _rawValue: T
  
  // 以后 ref 的依赖
  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) {newVal = this.__v_isShallow ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this.__v_isShallow ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

RefImpl的结构器接管两个值:value__v_isShallow是否浅层响应式。

constructor(value: T, public readonly __v_isShallow: boolean) {
  // 获取原始值,如果是浅层响应式,原始值就是 value;如果不是浅层响应式,原始值是 value 的原始值
  this._rawValue = __v_isShallow ? value : toRaw(value)
  // 响应式数据,如果是浅层响应式,是 value;否则转为 reactive(只有 Object 类型才会转为 reactive)this._value = __v_isShallow ? value : toReactive(value)
}

当获取 new RefImpl()value属性时,会调用 trackRefValue 进行依赖收集,并返回this._value

export function trackRefValue(ref: RefBase<any>) {if (shouldTrack && activeEffect) {ref = toRaw(ref)
    if (__DEV__) {trackEffects(ref.dep || (ref.dep = createDep()), {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      // 收集依赖到 ref.dep 中
      trackEffects(ref.dep || (ref.dep = createDep()))
    }
  }
}

reactive 不同,ref的依赖会被保留在 ref.dep 中。

当批改 new RefImpl()value属性时,会调用 triggerRefValue 触发依赖。

set value(newVal) {newVal = this.__v_isShallow ? newVal : toRaw(newVal)
  // 当 newVal 与旧原始值不同时,触发依赖
  if (hasChanged(newVal, this._rawValue)) {
    // 更新原始值及响应式数据
    this._rawValue = newVal
    this._value = this.__v_isShallow ? newVal : toReactive(newVal)
    triggerRefValue(this, newVal)
  }
}

shallowRef

shallowRef的实现同样通过 createRef 函数,不过参数 shallowtrue

export function shallowRef(value?: unknown) {return createRef(value, true)
}
const state = shallowRef({count: 1})

effect(() => {console.log(state.value.count)
})

// 不会触发副作用
state.value.count = 2

// 能够触发副作用
state.value = {count: 3}

为什么 state.value.count = 2 不触发副作用?
state初始化时,state._value就是 {count: 1},一个一般对象,当应用state.value.count = 2 设置值时,会先触发 get 函数返回 state._value,而后再批改state._value,因为state._value 是一般对象,所以不会有副作用触发。

而当应用 state.value = {count: 3} 形式进行批改时,会命中 set 函数,因为新的值与旧的原始值内存地址不同,所以会触发副作用。

triggerRef

强制触发 ref 的副作用函数。

export function triggerRef(ref: Ref) {triggerRefValue(ref, __DEV__ ? ref.value : void 0)
}

实现原理很简略,就是被动调用一下 triggerRefValue 函数。

因为深度响应式的 ref 会主动进行依赖的触发,所以 triggerRef 次要利用于 shallowRef 的外部值进行深度变更后,被动调用 triggerRef 以触发依赖。例如后面的例子:

const state = shallowRef({count: 1})

effect(() => {console.log(state.value.count)
})

// 不会触发副作用
state.value.count = 2

// 被动触发副作用
triggerRef(state)

// 能够主动触发副作用
state.value = {count: 3}

customRef

创立一个自定义的ref,显式申明对其依赖追踪和更新触发的管制形式。

如创立一个防抖 ref,即只在最近一次set 调用后的一段固定距离后再调用:

import {customRef} from 'vue'

export function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {get() {track()
        return value
      },
      set(newValue) {clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()}, delay)
      }
    }
  })
}

来看 customRef 的实现:

export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {return new CustomRefImpl(factory) as any
}

customRef返回一个 CustomRefImpl 实例。

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

CustomRefImpl的实现与 RefImpl 的实现差不多,都有个 valuegetset函数,只不过 getset 在外部会调用用户本人定义的 getset函数。当进行初始化时,会将收集依赖的函数与触发依赖的函数作为参数传递给factory,这样用户就能够本人管制依赖收集与触发的机会。

总结

ref的通过 class 实现,通过 class 的取值函数和存值函数进行依赖的收集与触发。

对于深度响应式的 ref,会在向value 属性赋值过程中,将新的值转为reactive,以达到深度响应式的成果。

正文完
 0