blog-md icon indicating copy to clipboard operation
blog-md copied to clipboard

vue3源码解析--ref类型

Open jiangjiu opened this issue 4 years ago • 0 comments

ref

ref类型是用来做基本类型包装的,一个普通的对象可以通过Proxy来做劫持,但是基本类型行不通,vue3的做法是直接把基本类型包装在一个object的.value中,同时直接劫持这个object.value的getter和setter来实现响应式。

isRef

const isRefSymbol = Symbol()

export interface Ref<T = any> {
  // This field is necessary to allow TS to differentiate a Ref from a plain
  // object that happens to have a "value" field.
  // However, checking a symbol on an arbitrary object is much slower than
  // checking a plain property, so we use a _isRef plain property for isRef()
  // check in the actual implementation.
  // The reason for not just declaring _isRef in the interface is because we
  // don't want this internal field to leak into userland autocompletion -
  // a private symbol, on the other hand, achieves just that.
  [isRefSymbol]: true
  value: T
}

export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
  return r ? r._isRef === true : false
}

判断Ref类型实际会使用_isRef这个key,因为效率更高,判断symbol属性慢得多。

之所以不在接口里声明_isRef是因为这个symbol是一个私有属性,不希望对外暴露。

ref & shallowRef

export function ref<T>(value: T): T extends Ref ? T : Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
  return createRef(value)
}

export function shallowRef<T>(value: T): T extends Ref ? T : Ref<T>
export function shallowRef<T = any>(): Ref<T | undefined>
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}


function createRef(value: unknown, shallow = false) {
  if (isRef(value)) {
    return value
  }
  if (!shallow) {
    value = convert(value)
  }
  const r = {
    _isRef: true,
    get value() {
      track(r, TrackOpTypes.GET, 'value')
      return value
    },
    set value(newVal) {
      value = shallow ? newVal : convert(newVal)
      trigger(
        r,
        TriggerOpTypes.SET,
        'value',
        __DEV__ ? { newValue: newVal } : void 0
      )
    }
  }
  return r
}

ref就是用来追踪基本类型的方法,xxx.value会被proxy依赖追踪。 shwllowRef只保证这个值本身,不会做深层转换,如果xxx.value = {...} ,这个对象就不会是响应式的了。

customRef


export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
  const { get, set } = factory(
    () => track(r, TrackOpTypes.GET, 'value'),
    () => trigger(r, TriggerOpTypes.SET, 'value')
  )
  const r = {
    _isRef: true,
    get value() {
      return get()
    },
    set value(v) {
      set(v)
    }
  }
  return r as any
}

可以自定义一个ref对象,自定义getter和setter,通常可以用来做debounce、observerble、返回promise等。

toRef && toRefs

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

export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K
): Ref<T[K]> {
  return {
    _isRef: true,
    get value(): any {
      return object[key]
    },
    set value(newVal) {
      object[key] = newVal
    }
  } as any
}

toRef可以把解构出来的属性值作为单项ref类型进行依赖追踪,同时原对象依然保持对这个key值的响应式。

export default {
  setup(props) {
    useSomeFeature(toRef(props, 'foo'))
  }
}

把某一个key值传给composition函数时尤其有用。

toRefs is useful when returning a reactive object from a composition function so that the consuming component can destructure / spread the returned object without losing reactivity:


function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  // logic operating on state

  // convert to refs when returning
  return toRefs(state)
}

export default {
  setup() {
    // can destructure without losing reactivity
    const { foo, bar } = useFeatureX()

    return {
      foo,
      bar
    }
  }
}

这个例子已经可以看出,toRefs适合在compostion函数中包裹返回值对象,这样当consumer解构返回值时,确保拎出来的值都是响应式的。

jiangjiu avatar Apr 21 '20 11:04 jiangjiu