【Vue源码分析】Refs

688 阅读7分钟

公共源码

import { track, trigger } from './effect'
import { TrackOpTypes, TriggerOpTypes } from './operations'
import { isArray, isObject, hasChanged } from '@vue/shared'
import { reactive, isProxy, toRaw, isReactive } from './reactive'
import { CollectionTypes } from './collectionHandlers'

declare const RefSymbol: unique symbol

export interface Ref<T = any> {
  value: T
    // 用此唯一key,来做Ref接口的一个描述符,给isRef函数做类型判断
  [RefSymbol]: true
  _shallow?: boolean
}

export type ToRef<T> = T extends Ref ? T : Ref<UnwrapRef<T>>
export type ToRefs<T = any> = {
  // #2687: somehow using ToRef<T[K]> here turns the resulting type into
  // a union of multiple Ref<*> types instead of a single Ref<* | *> type.
  [K in keyof T]: T[K] extends Ref ? T[K] : Ref<UnwrapRef<T[K]>>
}

// 判断val是否是对象(非null),来决定是否采用reactive进行Proxy代理
const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val

class RefImpl<T> {
  private _value: T

  public readonly __v_isRef = true

// 默认是可写,如果仅仅是可读直接返回,其他要转换,即是非null的对象就得走reactive逻辑进行代理
  constructor(private _rawValue: T, public readonly _shallow = false) {
      // 
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }
// 此处get和set处理于Proxy逻辑类似,get进行收集依赖,set进行响应依赖
  get value() {
    //   访问属性时,依赖收集ref类型的value属性
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newVal) {
    //   当属性有改变时,对新值触发响应,
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal //更新初始原始值
      this._value = this._shallow ? newVal : convert(newVal) //更新初始_value值
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}



function createRef(rawValue: unknown, shallow = false) {
    // 是Ref类型不处理,直接返回
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

isRef

功能

Checks if a value is a ref object。

源码

// 判断是否是Ref类型,传参可以是ref类型
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
  return Boolean(r && r.__v_isRef === true)
}

ref

功能

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value。

源码

// 函数重载

//  Ref函数可以处理任何类型,但一般用来处理基本类型(string/number/boolean等),引用类型会走Proxy,如果时Map类型呢?
export function ref<T extends object>(value: T): ToRef<T>
// Ref类型的值是UnwrapRef类型
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)
}

示例

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

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

如 果将对象分配为 ref 值,则可以通过 reactive 方法使该对象具有高度的响应式。

shallowRef

功能

创建一个ref,它跟踪自己的.value更改,但不会使其值成为响应式的

源码

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

示例

const foo = shallowRef({})
// 改变ref的 值是响应式的
foo.value = {}
// 但这个值不会被转换
isReactive(foo.value) //false

UnwrapRef

功能

如果参数为 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val。

源码

// 根据泛型T类型递归地展开(object, Array, ComputedRef, Ref)嵌套值并绑定, 主要作用是在以下嵌套情况中,关于ref类型的值,不用使用`.value`访问, 直接解构
// 以下类型声明是访问对象属性obj['prop'], 返回属性值

// 自动展开嵌套的值或者Ref


// corner case when use narrows type
// Ex. type RelativePath = string & { __brand: unknown }
// RelativePath extends object -> true
type BaseTypes = string | number | boolean

/**
 * This is a special exported interface for other packages to declare
 * additional types that should bail out for ref unwrapping. For example
 * \@vue/runtime-dom can declare it like so in its d.ts:
 *
 * ``` ts
 * declare module '@vue/reactivity' {
 *   export interface RefUnwrapBailTypes {
 *     runtimeDOMBailTypes: Node | Window
 *   }
 * }
 * ```
 *
 * Note that api-extractor somehow refuses to include `declare module`
 * augmentations in its generated d.ts, so we have to manually append them
 * to the final generated d.ts in our build process.
 */
export interface RefUnwrapBailTypes {}

export type UnwrapRef<T> = T extends Ref<infer V>
  ? UnwrapRefSimple<V>
  : UnwrapRefSimple<T>

type UnwrapRefSimple<T> = T extends
  | Function
  | CollectionTypes
  | BaseTypes
  | Ref
  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
  ? T
  : T extends Array<any>
    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }
    : T extends object ? UnwrappedObject<T> : T

// Extract all known symbols from an object
// when unwrapping Object the symbols are not `in keyof`, this should cover all the
// known symbols
type SymbolExtract<T> = (T extends { [Symbol.asyncIterator]: infer V }
  ? { [Symbol.asyncIterator]: V }
  : {}) &
  (T extends { [Symbol.hasInstance]: infer V }
    ? { [Symbol.hasInstance]: V }
    : {}) &
  (T extends { [Symbol.isConcatSpreadable]: infer V }
    ? { [Symbol.isConcatSpreadable]: V }
    : {}) &
  (T extends { [Symbol.iterator]: infer V } ? { [Symbol.iterator]: V } : {}) &
  (T extends { [Symbol.match]: infer V } ? { [Symbol.match]: V } : {}) &
  (T extends { [Symbol.matchAll]: infer V } ? { [Symbol.matchAll]: V } : {}) &
  (T extends { [Symbol.replace]: infer V } ? { [Symbol.replace]: V } : {}) &
  (T extends { [Symbol.search]: infer V } ? { [Symbol.search]: V } : {}) &
  (T extends { [Symbol.species]: infer V } ? { [Symbol.species]: V } : {}) &
  (T extends { [Symbol.split]: infer V } ? { [Symbol.split]: V } : {}) &
  (T extends { [Symbol.toPrimitive]: infer V }
    ? { [Symbol.toPrimitive]: V }
    : {}) &
  (T extends { [Symbol.toStringTag]: infer V }
    ? { [Symbol.toStringTag]: V }
    : {}) &
  (T extends { [Symbol.unscopables]: infer V }
    ? { [Symbol.unscopables]: V }
    : {})

type UnwrappedObject<T> = { [P in keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>

示例

function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x) // unwrapped 确保现在是数字类型
}

toRef

功能

可以用来为源响应式对象上的 property 性创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接。

源码

class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true

  constructor(private readonly _object: T, private readonly _key: K) {}

// 增加value属性
  get value() {
    return this._object[this._key]
  }

  set value(newVal) {
    this._object[this._key] = newVal
  }
}

export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K
): ToRef<T[K]> {
  return isRef(object[key])
    ? object[key]
    : (new ObjectRefImpl(object, key) as any)
}

示例

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

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) // 2

state.foo++
console.log(fooRef.value) // 3

当您要将 prop 的 ref 传递给复合函数时,toRef 很有用:

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

toRefs

功能

将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的ref。

源码

// 将响应式对象
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
}

示例

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

const stateAsRefs = toRefs(state)
/*
Type of stateAsRefs:

{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// ref 和 原始property “链接”
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3

当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行分解/扩散:

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

  // 逻辑运行状态

  // 返回时转换为ref
  return toRefs(state)
}

export default {
  setup() {
    // 可以在不失去响应性的情况下破坏结构
    const { foo, bar } = useFeatureX()

    return {
      foo,
      bar
    }
  }
}

customRef

功能

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并应返回一个带有 get 和 set 的对象

源码

export type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}

class CustomRefImpl<T> {
  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(
      () => track(this, TrackOpTypes.GET, 'value'),
      () => trigger(this, TriggerOpTypes.SET, 'value')
    )
    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
}

示例

使用 v-model 使用自定义 ref 实现 debounce 的示例:

<input v-model="text" />

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

export default {
  setup() {
    return {
      text: useDebouncedRef('hello')
    }
  }
}

Typing:

function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}

unref

功能

如果参数为 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val。

源码

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

示例

function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x) // unwrapped 确保现在是数字类型
}

triggerRef

功能

手动执行与 shallowRef](#shallowref) 关联的任何效果。

源码

export function triggerRef(ref: Ref) {
  trigger(toRaw(ref), TriggerOpTypes.SET, 'value', __DEV__ ? ref.value : void 0)
}

示例

const shallow = shallowRef({
  greet: 'Hello, world'
})

// 第一次运行时记录一次 "Hello, world"
watchEffect(() => {
  console.log(shallow.value.greet)
})

// 这不会触发作用,因为 ref 很浅层
shallow.value.greet = 'Hello, universe'

// 记录 "Hello, universe"
triggerRef(shallow)

proxyRefs

功能

对ref类型进行代理

源码

export type ShallowUnwrapRef<T> = {
  [K in keyof T]: T[K] extends Ref<infer V> ? V : T[K]
}


const shallowUnwrapHandlers: ProxyHandler<any> = {
  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  set: (target, key, value, receiver) => {
    const oldValue = target[key]
    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)
}

UnwrapRef是自动展开嵌套的值或者Ref

总结

  • Ref函数可以处理任何类型,但一般用来处理基本类型(string/number/boolean等),引用类型(Map\Set\Object等)会走reactive,也就是会走Proxy。

  • 对于基本数据类型,函数传递或者对象解构时,会丢失原始数据的引用,换言之,我们没法让基本数据类型,或者解构后的变量(如果它的值也是基本数据类型的话),成为响应式的数据。

  • 通过创建一个对象(Ref), 将原始数据保存在Ref的属性value当中,再将它的引用返回给使用者。

  • 通过toRefs(object)函数, 解决对象的解构丢失原始数据引用的问题。通过遍历对象,将每个属性值都转成Ref数据,这样解构出来的还是Ref数据,自然就保持了响应式数据的引用 toRefs 解决的问题就是,开发者在函数中错误的解构 reactive,来返回基本类型。

const { x, y } = = reactive({ x: 1, y: 2 }),这样会使 x, y 失去响应式,于是官方提出了 toRefs 方案,在函数返回时,将 reactive 转为 refs,来避免这种情况。