isRef、unRef、toRef和proxyRefs等API原理

267 阅读4分钟

isRef

是用于判断一个对象是否被ref实例对象所包裹;

export function isRef(r: any): r is Ref {
  return !!(r && r.__v_isRef === true)
}

unRef

省略ref调用值时的.value​操作,直接进行数据的操作获取;
当使用.value​太频繁的时候,不知道后面的值到底有没有.value​,此时就可以用该API进行包裹访问

export function unref<T>(ref: MaybeRef<T> | ComputedRef<T> | ShallowRef<T>): T {
  return isRef(ref) ? ref.value : ref
}

proxyRefs

当访问的是 ref 类型的值时 返回.value;

当访问的不是 ref 类型的值时 直接返回value;

帮助我们脱离ref的value限制,让我们可以直接访问响应式数据,而不需要通过value间接访问

在Vue3中的setup中,不管在模板中有没有使用.value​,都可以进行.value​的省略,因为setup内部返回的结果就调用了这个API​

  • proxyRefs需求分析

    • ​proxyRefs​可以对数据进行get​和set​,并且还有对应的处理
    • 在get​的时候,会自动把.value​省略掉
    • 在set​的时候,需要区分是普通值还是ref;proxyUser.age = ref(10)​

功能实现

  • 由于需要劫持对象,因此需要使用Proxy​来进行代理劫持

  • 在进行get​的时候,判断获取的结果是ref​还是普通的,如果是ref​的话,默认调用.value​

  • 对于set​的话,同样需要判断set​的内容是不是ref​,且需要判断set​之前的值是什么类型的

    • ​set​的是内容是普通值,且原来的值是ref​,需要调用.value​来赋值
    • 否则,直接替换即可
export function proxyRefs<T extends object>(
  objectWithRefs: T,
): ShallowUnwrapRef<T> {
  return isReactive(objectWithRefs)
    ? objectWithRefs
    : new Proxy(objectWithRefs, shallowUnwrapHandlers)
}


const shallowUnwrapHandlers: ProxyHandler<any> = {
  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  set: (target, key, value, receiver) => {
    const oldValue = target[key]
	// set需要判断新内容和set之前的内容是否为ref 
    if (isRef(oldValue) && !isRef(value)) {
     // 新值是普通值,原来的值是ref类型 - 调用.value来替换
      oldValue.value = value
      return true
    } else {
     // 直接替换
      return Reflect.set(target, key, value, receiver)
    }
  },
}

shallowRef

只处理基本数据类型的响应式,不进行对象的响应式处理

  • 有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换时使用shallowRef​
  • 当使用该API代理对象的时候,对象默认是不带有响应式的,需要进行强制更新页面DOM的操作 - triggerRef​
const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
// 会触发更改
state.value = { count: 2 }
export function shallowRef<T>(
  value: T,
): Ref extends T
  ? T extends Ref
    ? IfAny<T, ShallowRef<T>, T>
    : ShallowRef<T>
  : ShallowRef<T>
export function shallowRef<T = any>(): ShallowRef<T | undefined>
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}

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

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)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      const oldVal = this._rawValue
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, DirtyLevels.Dirty, newVal, oldVal)
    }
  }
}

toRef

访问代理:解决响应式数据丢失的问题

可以为源响应式对象上的某个属性新创建一个ref,且ref可以被传递​,会保持对源属性的响应式连接​


import { ref, toRef } from "vue";
// 使用 toRef 后 两个变量数据 会产生链式关系 互相响应 一个数据发送改变 另一个也会跟随 改变
const user = ref({
  name: "Lbxin",
  age: 22,
});

const newAge = toRef(user.value, "age");

newAge.value = 20;
console.log(user.value.age); // 20

user.value.age = 18;
console.log(newAge.value); // 18

实现思路

接收两个参数,第一个是响应式数据,第二个是响应式数据的一个键,通过返回类似Ref​结构的对象进行响应式获取

// 访问子属性.value相当于访问父对象.子属性 底层还是proxy
export function toRef(
  source: Record<string, any> | MaybeRef,
  key?: string,
  defaultValue?: unknown,
): Ref {
  if (isRef(source)) {
    return source
  } else if (isFunction(source)) {
    return new GetterRefImpl(source) as any
  } else if (isObject(source) && arguments.length > 1) {
    return propertyToRef(source, key!, defaultValue)
  } else {
    return ref(source)
  }
}

function propertyToRef(
  source: Record<string, any>,
  key: string,
  defaultValue?: unknown,
) {
  const val = source[key]
  return isRef(val)
    ? val
    : (new ObjectRefImpl(source, key, defaultValue) as any)
}

class GetterRefImpl<T> {
  public readonly __v_isRef = true
  public readonly __v_isReadonly = true
  constructor(private readonly _getter: () => T) {}
  get value() {
    return this._getter()
  }
}

toRefs

访问代理:解决响应式数据丢失的问题

将一个响应式对象转换为普通对象,但是内部的数据还是响应式的


import { ref, toRefs } from "vue";
// 使用 toRef 后 两个变量数据 会产生链式关系 互相响应 一个数据发送改变 另一个也会跟随 改变
const user = ref({
  name: "Lbxin",
  age: 22,
});
const newAge = toRefs(user.value);

newAge.age.value = 20;
console.log(user.value.age); // 20

user.value.age = 18;
console.log(newAge.value); // 18

实现思路

  • 将响应式数据转换为类似于ref​结构的数据
  • 在某些方面上来说,toRefs​转换后的结果要视为真正的ref​数据
export function toRefs(object) {
  const res = {};
  for (let key in object) {
    // 挨个属性调用toRef
    res[key] = toRef(object, key);
  }
  return res;
}