Vue3中ref()、toRef()、toRefs()、unref()等响应式函数

787 阅读5分钟

一、背景

Vue3组合式API中响应式相关的函数很多,使用中容易混淆,本文将整理并分析这些函数的异同点,使用场景,方便以后能正确的,更好的使用这些函数。主要整理的函数如下:

1、ref()、reactive()

2、shallowRef()、shallowReactive()

3、toRef()、toRefs()

4、unref()、toValue()、toRaw()

5、isRef()、isProxy()、isReactive()

二、详细分析

1、ref()、reactive()

相同点: 均能声明响应式对象,且声明的响应式对象都是深层的。

不同点: ref()既可以接收原始类型数据,又可以接收对象类型数据为参数,返回一个带value属性的响应式对象;reactive()只能接收对象类型数据为参数,返回原对象的响应式代理。由于reactive()的一些局限性,官方建议使用ref()声明响应式数据。

  • ref():可以接收原始类型如number、boolean、string或者对象类型如对象,数组,Map,Set的数据为参数,返回一个带value属性的响应式对象,且这个ref对象是深层响应的。
import { ref } from 'vue'
// 1. 接收原始类型数据
const state = ref(0)
console.log('state', state)
watchEffect(() => {
  console.log('state.value', state.value)
})
state.value++

image.png

// 2. 接收对象类型数据
const state = ref({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})
console.log('state', state)

watchEffect(() => {
  console.log('state', state.value.nested.count)
})
function mutateDeeply() {
  state.value.nested.count++
  state.value.arr.push('baz')
}
mutateDeeply()

image.png

  • reactive():接收对象类型(对象,数组,Map,Set)为参数,返回原始对象的响应式代理。响应是深层的,即代理对象的嵌套对象依然是响应式代理。有以下缺点:只能接收对象类型(对象,数组,Map,Set),不能接收string,number,boolean这样的原始类型为参数;不能替换整个对象,这样做会失去响应性连接;解构或者将对象的属性传递给函数时,失去响应性连接。
import { reactive } from 'vue'
// 1. 只能接收对象类型数据
let state = reactive({ count0 })
let state1 = reactive(1)
console.log('state', state)
console.log('state1', state1)

image.png

// 2. 不能替换整个对象
let state = reactive({ count1 })
watchEffect(() => {
  console.log('state', state)
})
// state与{count: 1}的响应性连接丢失
state = reactive({ name0 })

image.png

// 3. 解构的属性丢失响应性连接
const state = reactive({ count0 })
let { count } = state
count++
console.log('count', count)
console.log('state', state)

function test(param){
  console.log('param', param)
}
// 函数接收到的只是一个普通的数字,不会追踪state.count的变化
test(state.count)

image.png

2、shallowRef()、shallowReactive()

相同点: 均能生成浅层响应式数据,即只有数据顶层是响应式的。

不同点: shallowRef()是对应于ref()的浅层作用形式,shallowReactive()是对应reactive()的浅层作用形式。

  • shallowRef():生成浅层响应式数据,只有.value的访问被追踪,可以用来优化大型不可变数据的响应性开销、与外部状态系统集成。
import { shallowRef } from 'vue'
const state = shallowRef({ count1 })
watchEffect(() => {
  console.log('state.value.count', state.value.count)
})
state.value.count = 2 // 非响应
state.value = { count3 } // 响应

image.png

  • shallowReactive():shollowReactive()返回的数据,只有根级别的属性是响应式的,属性的值会被原样存储和暴露,即非响应式。
import { shallowReactive } from 'vue'
const state = shallowReactive({
  foo1,
  nested: {
    bar2
  }
})

watchEffect(() => {
  console.log(state.nested)
})

// 更改根属性是响应式的
state.foo++ 
console.log('isReactive(state.nested)', isReactive(state.nested))
console.log('state.nested', state.nested)

// 不是响应式
state.nested.bar++

image.png

3、toRef()、toRefs()

相同点: 都可以将响应式对象的属性转成ref,并且这个ref和原属性保持同步:改变原属性的值将更新ref的值,反之亦然。

不同点: toRef()只能转换响应式对象上面的一个属性,toRefs是将响应式对象的所有属性都转换为ref。

  • toRef():toRef()除了能将响应式对象上的一个属性转化为ref,还可以将值、refs或getters规范为refs,可以用于把一个prop的ref传递给一个组合式函数时。
// 转化响应式对象上的属性为ref
const state = reactive({
  foo1,
  bar2
})

// 双向ref, 会与源属性同步
const fooRef = toRef(state, 'foo')

// 更改该ref会更新源属性
fooRef.value++
console.log('state.foo', state.foo)

// 更改源属性也会更新该ref
state.foo++
console.log('fooRef.value', fooRef.value)

image.png

import { toRef } from 'vue'
// 规范化签名
const props = defineProps({
  foo: {
    typeString,
    default'foo'
  }
})

// 等同于 ref(1)
toRef(1)

const ref1 = ref(0)
const ref2 = toRef(ref1)
console.log('ref2 === ref1', ref2 === ref1) // true

// 创建一个只读的ref,当访问 .value 时会调用此 getter 函数
const ref3 = toRef( () => props.foo )
console.log('ref3', ref3)
console.log('ref3.value', ref3.value)

// 把一个 prop 的 ref 传递给一个组合式函数
useSomeFeature(toRef(() => props.foo))

image.png

  • toRefs():将一个响应式对象转化为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref,和原响应式对象对应的属性保持同步。
import { toRefs } from 'vue'
const state = reactive({
  foo1,
  bar2
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

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

可用于从组合式函数中返回响应式对象,这样解构返回的对象时不会失去响应性。

function useFeatureX() {
  const state = reactive({
    foo1,
    bar2
  })
  // 在返回时都转为 ref
  return toRefs(state)
}

// 可以解构而不会失去响应性
const { foo, bar } = useFeatureX()

4、unref()、toValue()、toRaw()

相同点: 都可以返回响应式数据的原始值。

不同点: unref(),toValue()只能返回ref(),shallowRef()创建的ref对象的内部值;toRaw()能返回Proxyreactive()、shallowReactive()、readonly()、shallowReadonly()创建的代理对象的原始对象。

  • unRef():参数为ref时,返回ref的内部值,否则返回参数本身,比如如果参数是reactive响应值,返回其本身。
import { unref, shallowRef } from 'vue'
const test1 = ref(1)
console.log('unref(test1)', unref(test1))

const test2 = shallowRef(2)
console.log('unref(test2)', unref(test2))

const test3 = reactive({a:3})
console.log('unref(test3):', unref(test3))

const test4 = ref({a:1})
console.log('unref(test4)',  unref(test4))

image.png

  • toValue():与unref()类似,不同的是能返回getter函数的值,可以用于组合式函数中规范化一个可以是值、ref 或 getter 的参数。
import { toValue } from 'vue'
toValue(1) //       --> 1
toValue(ref(1)) //  --> 1
toValue(() => 1) // --> 1
  • toRaw():可以返回由reactive()、readonly()、shallowReactive()、shallowReadonly()创建的代理对象的原始对象。
import { toRaw } from 'vue'
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true

5、isRef()、isProxy()、isReactive()

相同点: 判断是否是响应式数据。

不同点: isRef()检查某个值是否为ref;isProxy()检查一个对象是否是由reactive(), readonly(), shallowReactive(), shallowReadonly()创建;isReactive()检查一个对象是否由reactive()或shallowReactive()创建。