传送门:Vue3中 响应式 API( shallowReactive、shallowRef、triggerRef 、customRef )详解
传送门:Vue3中 响应式 API ( readonly、shallowReadonly、toRaw、markRaw ) 详解
我们在写项目中,总会遇到变量的定义,在 Vue2 里面,我们只需要直接定义变量就行,但是在 Vue3 里面,官方为我们推出了定义变量的方式,那么接下来我们来看看,官方给出的函数:reactive、ref、toRef、toRefs。
reactive 会对传入的引用类型进行包裹,创建一个该对象的 Proxy 代理。它是源对象的响应式副本,不等于原始对象。它“深层”转换了源对象的所有嵌套 property,解包并维持其中的任何 ref 引用关系。
<template>
<div>
<div>{{obj.name}}</div>
<div>{{obj.age}}</div>
<button @click="changeName">修改</button>
</div>
</template>
<script>
import { reactive} from 'vue';
export default {
setup(){
const obj = reactive({
name:'张三',
age:18,
});
// 使用 reactive 定义的属性可直接使用,不需要加 .value
const changeName = () => {
obj.name = '李四';
obj.age = 12;
}
return {
obj,
changeName
}
}
}
</script>
reactive 将解包所有深层的 refs,同时维持 ref 的响应性。
const count = ref(1)
const obj = reactive({ count })
// ref 会被解包
console.log(obj.count === count.value) // true
// 它会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2
// 它也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3
当将 ref 分配给 reactive property 时,ref 将被自动解包。
const count = ref(1)
const obj = reactive({})
obj.count = count
console.log(obj.count) // 1
console.log(obj.count === count.value) // true
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。
<template>
<div>
<div>{{city}}</div>
<div>{{obj.name}}</div>
<button @click="changeData">修改</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup(){
const city = ref('西安');
const obj = ref({
name:'张三'
})
const changeData = () => {
city.value = '北京';
obj.value.name = '李四';
console.log(city,obj)
}
return {
city,
obj,
changeData
}
}
}
</script>
从定义数据角度对比:
1. ref 用来定义:基本类型数据;
2. reactive 用来定义:对象(或数组)类型(引用类型);
3. 备注:ref 也可以用来定义对象(或数组)类型数据, 它内部会自动通过 reactive 转为代理对象;
从原理角度对比:
1. ref 通过 Object.defineProperty() 的 get 与 set 来实现响应式(数据劫持);
2. reactive 通过使用 Proxy 来实现响应式(数据劫持), 并通过 Reflect 操作源对象内部的数据;
从使用角度对比:
1. ref 定义的数据:操作数据需要 .value,读取数据时模板中直接读取不需要 .value;
2. reactive 定义的数据:操作数据与读取数据:均不需要 .value;
可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
<template>
<div>
<div>{{obj.city}}</div>
<div>{{city}}</div>
<button @click="changeData">修改</button>
</div>
</template>
<script>
import { reactive, toRef} from 'vue';
export default {
setup(){
const obj = reactive({
name:'张三',
city:'西安',
});
// let {city} = obj; // 注意:从响应式数据对象中解构出的属性数据,不再是响应式数据
let city = toRef(obj,'city');
const changeData = () => {
city.value = '北京';
console.log(city,obj)
}
return {
obj,
city,
changeData
}
}
}
</script>
<template>
<div>
<div>ref:{{state1.city}}</div>
<div>toRef:{{state2}}</div>
<button @click="changeCity1">修改city1</button>
<button @click="changeCity2">修改city2</button>
</div>
</template>
<script>
import { ref, toRef} from 'vue';
export default {
setup(){
const obj = {
name:'张三',
city:'西安',
};
let state1 = ref(obj);
let state2 = toRef(obj,'city');
const changeCity1 = () => {
state1.value.city = '北京';
console.log('ref:',obj,state1,state2);
};
const changeCity2 = () => {
state2.value = '深圳';
console.log('toRef:',obj,state1,state2);
}
return {
state1,
state2,
changeCity1,
changeCity2,
}
}
}
</script>
当执行 changeCity1 时,我们发现打印的 state1 的 city 属性 和 state 2 发生了改变,同时页面上的 state1 的 city 属性 和 state2 也发生了改变。
当执行 changeCity2 时,我们发现打印的 state1 的 city 属性 和 state 2 发生了改变,但页面上的 state1 的 city 属性 和 state2 未发生了改变。
当要将 prop 的某个 ref (即用 ref 包装的属性) 传递给复合函数时,toRef 很有用:
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo'))
}
}
即使源 property 不存在,toRef 也会返回一个可用的 ref。这使得它在使用可选 prop 时特别有用,因为可选 prop 并不会被 toRefs 处理。
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。
<template>
<div>
<div>{{city}}</div>
<button @click="changeData">修改</button>
</div>
</template>
<script>
import { reactive,toRefs } from 'vue';
export default {
setup(){
const obj = reactive({
name:'张三',
city:'西安',
});
let obj2 = toRefs(obj);
const changeData = () => {
obj2.city.value = '北京';
console.log(obj,obj2)
};
return {
...obj2,
changeData
}
}
}
</script>
实际开发中,比如需要解构 props,就可以这样操作来保证与 props 参数的响应式引用:
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
但如果 title 是可选 prop,则传入的 props 中可能没有 title 。这种情况下,toRefs 将不会为 title 创建一个 ref ,此时就需要用 toRef 替代它:
import { toRef } from 'vue'
setup(props) {
const title = toRef(props, 'title')
console.log(title.value)
}
像上面这样做确保我们的侦听器能够根据 title prop 的变化做出反应。
当从组合式函数返回响应式对象时, toRefs 非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行分解/扩散:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// ... 操作 state 的逻辑
// 返回时转换为ref
return toRefs(state)
}
export default {
setup() {
// 可以在不失去响应性的情况下解构
const { foo, bar } = useFeatureX()
return {
foo,
bar
}
}
}
检查对象是否是由 reactive 创建的响应式代理。
<script>
import { isReactive, reactive } from 'vue';
export default {
setup(){
const state = { count:0 };
const obj = reactive({
name:'张三',
city:'西安',
});
console.log(isReactive(obj)); // true
console.log(isReactive(state)); // false
return {}
}
}
</script>
检查值是否为一个 ref 对象
<script>
import { isRef, reactive,ref, toRef } from 'vue';
export default {
setup(){
const state = { count:0 };
const obj = ref({ name:'张三'});
const obj2 = reactive({name:'张三'});
const name = toRef(obj2,'name');
console.log(isRef(obj)); // true
console.log(isRef(state)); // false
console.log(isRef(name)); // true
return {}
}
}
</script>
如果参数是一个 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 的语法糖函数。
<script>
import {ref, unref} from 'vue';
export default {
setup(){
const state = { count:0 };
const obj = ref({ name:'张三'});
console.log(unref(state)); // { count:0 }
console.log(unref(obj)); // Proxy {name: '张三'}
return {}
}
}
</script>
检查对象是否是由 reactive 或 readonly 创建的 proxy。
<script>
import { reactive,readonly,isProxy} from 'vue';
export default {
setup(){
const state = { count:0 };
const obj = reactive({ name:'张三'});
const obj2 = readonly({name:'张三'});
console.log(isProxy(obj)); // true
console.log(isProxy(obj2)); // true
console.log(isProxy(state)); // false
return {}
}
}
</script>