一、背景
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++
// 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()
- reactive():接收对象类型(对象,数组,Map,Set)为参数,返回原始对象的响应式代理。响应是深层的,即代理对象的嵌套对象依然是响应式代理。有以下缺点:只能接收对象类型(对象,数组,Map,Set),不能接收string,number,boolean这样的原始类型为参数;不能替换整个对象,这样做会失去响应性连接;解构或者将对象的属性传递给函数时,失去响应性连接。
import { reactive } from 'vue'
// 1. 只能接收对象类型数据
let state = reactive({ count: 0 })
let state1 = reactive(1)
console.log('state', state)
console.log('state1', state1)
// 2. 不能替换整个对象
let state = reactive({ count: 1 })
watchEffect(() => {
console.log('state', state)
})
// state与{count: 1}的响应性连接丢失
state = reactive({ name: 0 })
// 3. 解构的属性丢失响应性连接
const state = reactive({ count: 0 })
let { count } = state
count++
console.log('count', count)
console.log('state', state)
function test(param){
console.log('param', param)
}
// 函数接收到的只是一个普通的数字,不会追踪state.count的变化
test(state.count)
2、shallowRef()、shallowReactive()
相同点: 均能生成浅层响应式数据,即只有数据顶层是响应式的。
不同点: shallowRef()是对应于ref()的浅层作用形式,shallowReactive()是对应reactive()的浅层作用形式。
- shallowRef():生成浅层响应式数据,只有.value的访问被追踪,可以用来优化大型不可变数据的响应性开销、与外部状态系统集成。
import { shallowRef } from 'vue'
const state = shallowRef({ count: 1 })
watchEffect(() => {
console.log('state.value.count', state.value.count)
})
state.value.count = 2 // 非响应
state.value = { count: 3 } // 响应
- shallowReactive():shollowReactive()返回的数据,只有根级别的属性是响应式的,属性的值会被原样存储和暴露,即非响应式。
import { shallowReactive } from 'vue'
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
watchEffect(() => {
console.log(state.nested)
})
// 更改根属性是响应式的
state.foo++
console.log('isReactive(state.nested)', isReactive(state.nested))
console.log('state.nested', state.nested)
// 不是响应式
state.nested.bar++
3、toRef()、toRefs()
相同点: 都可以将响应式对象的属性转成ref,并且这个ref和原属性保持同步:改变原属性的值将更新ref的值,反之亦然。
不同点: toRef()只能转换响应式对象上面的一个属性,toRefs是将响应式对象的所有属性都转换为ref。
- toRef():toRef()除了能将响应式对象上的一个属性转化为ref,还可以将值、refs或getters规范为refs,可以用于把一个prop的ref传递给一个组合式函数时。
// 转化响应式对象上的属性为ref
const state = reactive({
foo: 1,
bar: 2
})
// 双向ref, 会与源属性同步
const fooRef = toRef(state, 'foo')
// 更改该ref会更新源属性
fooRef.value++
console.log('state.foo', state.foo)
// 更改源属性也会更新该ref
state.foo++
console.log('fooRef.value', fooRef.value)
import { toRef } from 'vue'
// 规范化签名
const props = defineProps({
foo: {
type: String,
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))
- toRefs():将一个响应式对象转化为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref,和原响应式对象对应的属性保持同步。
import { toRefs } from 'vue'
const state = reactive({
foo: 1,
bar: 2
})
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({
foo: 1,
bar: 2
})
// 在返回时都转为 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))
- 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()创建。