Vue3:简单使用 toRef、toRefs、toRaw

5,520 阅读3分钟

[vue3源码](GitHub - vuejs/core: 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.)

toRef

  1. 创建一个基于响应式对象对应的ref
import { reactive, toRef } from 'vue';

const data = reactive({ heroName: "张郃" })

const heroName: any = toRef(data, 'heroName')

const change = () => {
  heroName.value = '荀彧'
  console.log('修改后heroName.value: ', heroName.value); // 修改后heroName.value:  荀彧
}
  <div>
    <div>{{ heroName }}</div>
    <button @click="change">修改</button>
  </div>
  1. 对非响应式对象使用toRef,值虽然会发生改变,但是视图不会更新
import { toRef } from 'vue';

const data = { heroName: "张郃" } // <---------- 普通对象

const heroName: any = toRef(data, 'heroName') // <------ 没啥用

const change = () => {
  heroName.value = '荀彧'
  console.log('修改后heroName.value: ', heroName.value); // 修改后heroName.value:  荀彧
}
  <div>
    <div>{{ rData }}</div>
    <button @click="change">修改</button>
  </div>

toRefs

  1. 把响应式对象中的所有属性,都变成 ref
import { reactive, toRefs, onMounted } from 'vue'

const data = reactive({ a: "赵云", b: '周泰', c: '许褚' })
const data2 = reactive({ d: '夏侯惇', e: '张辽', f: '孙尚香' })

const { a, b, c } = toRefs(data)
const { d, e, f } = data2

onMounted(() => {
  console.group("== 使用 toRefs 方式的 ==");
  console.log(a);
  console.log(b);
  console.log(c);
  console.groupEnd();

  console.group("== 不使用 toRefs 方式的 ==");
  console.log(d);
  console.log(e);
  console.log(f);
  console.groupEnd();
})

1.jpg

toRefs 实际上是把响应式对象中的每一个属性都调用了一遍toRef

toRaw

  1. 根据响应式对象,返回其普通(原始)对象,失去响应式
import { reactive, toRaw, onMounted } from 'vue'

const data = reactive({ name: "马云禄" })

const rawData = toRaw(data)

onMounted(() => {
  console.log(data); // Proxy {name: '马云禄'}
  console.log(rawData); // {name: '马云禄'}
  console.log(data['__v_raw']); // '__v_raw' 是源码中的内容
})

3.png

源码

toRef、toRefs源码地址: \core-main\packages\reactivity\src\ref.ts

toRaw 源码地址: \core-main\packages\reactivity\src\reactive.ts

toRef

export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>

export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K
): ToRef<T[K]>

export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue: T[K]
): ToRef<Exclude<T[K], undefined>>

// 上面是函数重载,不用管它
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue?: T[K]
): ToRef<T[K]> {
  const val = object[key] // 接受两个参数,一是响应式对象,二是响应式对象中对应的键
  
  // 是不是 ref 对象, 是 -> 返回我们的响应式对象 不是 -> 变成响应式对象 
  return isRef(val)
    ? val
    : (new ObjectRefImpl(object, key, defaultValue) as any)
}

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

可以看到 toRef 并没有像 refreactive 那样去调用收集依赖、更新依赖的方法,因此它是对非响应式对象没有任何作用(页面不会更新!)

toRefs

export type ToRefs<T = any> = {
  [K in keyof T]: ToRef<T[K]>
}
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 in 来对每一个属性进行 toRef,实现响应式
  for (const key in object) {
    ret[key] = toRef(object, key)
  }
  // 最后返回
  return ret
}

toRaw

export function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}

实际上就是在对象中取属性

[ReactiveFlags.RAw]是个一个枚举类型

export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw'
}

总结

toRef

  1. 根据一个代理对象的键,返回对应的 ref
  2. 对非代理对象使用toRef,没有任何效果(视图不会更新)
  3. 当函数参数需要reactive代理对象中的某个属性时,可以使用toRef单独传递

toRefs

  1. 当我们使用reactive去创建代理对象时,如果有需要,可以使用toRefs把代理对象中所有的属性都变成ref

toRaw

  1. 把代理对象变为普通(原始)对象,失去响应式