Vue3系列(三)Composition Api之to系列(附源码解析)

427 阅读3分钟

toRef

可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,ref 函数也可以转换,但值非关联。

访问对象属性时 记得使用 toRef 或者 toRefs进行解构

// 引用场景
const person = reactive({
    name: '小张',
    age: 12
})
// 双向 ref,会与源属性同步
const nameToRef = toRef(person, 'name')

const change = () => {
    nameToRef.value = '大张'
    console.log(person) // { name: '大张', age: 12 }
    
    // 更改源属性也会更新该 ref
    person.name = '无名'
    console.log(nameToRef.value) // 无名
}

toRef 源码解析

如果是ref 对象则直接返回,如果不是则通过ObjectRefImpl创建一个类ref对象

export function toRef(
  source: Record<string, any> | MaybeRef,
  key?: string,
  defaultValue?: unknown
): Ref {
  if (isRef(source)) {
    // 以及是一个ref对象了就直接返回
    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: object, key: string, defaultValue?: unknown) {
  const val = (source as any)[key]
  return isRef(val)
    ? val
    : (new ObjectRefImpl(  // 通过ObjectRefImpl创建一个类ref、ObjectRefImpl源码在下面
        source as Record<string, any>,
        key,
        defaultValue
      ) as any)
}

类ref对象只是做了值的改变, 并未处理收集依赖触发依赖的过程,所以普通对象无法更新视图。 与ref的源码类似,不过ref是通过RefImpl类创建,里面处理了收集依赖触发依赖的过程

ObjectRefImpl

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

  constructor(
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K]
  ) {}

  get value() {
    const val = this._object[this._key]
    return val === undefined ? (this._defaultValue as T[K]) : val
  }

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

  get dep(): Dep | undefined {
    return getDepFromReactive(toRaw(this._object), this._key)
  }
}

toRefs

将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref

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

// 这个 ref 和源属性已经“链接上了” 
state.foo++
console.log(stateAsRefs.foo.value) // 2 

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

配合解构使用

import { reactive, toRefs } from 'vue'

const state = reactive({
  foo: 1,
  bar: 1
})
 
let { foo, bar } = state(obj)
 
foo.value++
bar.value = 5
console.log(state.foo, state.bar); // 2, 5

toRefs 源码解析

把reactive 对象的每一个属性通过对象循环调用toRef()都变成了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] = propertyToRef(object, key) // 通过toRef()把响应式对象的每个属性都变成 ref
  }
  return ret
}

toRaw

根据一个 Vue 创建的代理返回其原始对象,也就是将响应式对象转化为普通对象

import { reactive, toRaw } from 'vue'
 
const state = reactive({
   foo: 1,
   bar: 1
})
 
 
const obj = toRaw(state)
// 响应式对象转化为普通对象
 
console.log(state, obj) // proxy object

console.log(state['__v_raw']) // 可以直接取到原始对象,具体可以看下面源码

toRaw 源码解析

export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw'
}
 
export function toRaw<T>(observed: T): T {
  // 通过 ReactiveFlags 枚举值取出 proxy对象的原始对象
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}

toValue

将值、refs 或 getters 规范化为值。这与 unref() 类似,不同的是此函数也会规范化 getter 函数。如果参数是一个 getter,它将会被调用并且返回它的返回值。

toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1