美文网首页【vue3源码】
【vue3源码】九、ref源码解析

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

作者: MAXLZ | 来源:发表于2022-08-31 21:28 被阅读0次

    【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,以达到深度响应式的效果。

    相关文章

      网友评论

        本文标题:【vue3源码】九、ref源码解析

        本文链接:https://www.haomeiwen.com/subject/xkfjnrtx.html