传送门:Vue3中 响应式 API( shallowReactive、shallowRef、triggerRef 、customRef )详解
传送门:Vue3中 响应式 API ( readonly、shallowReadonly、toRaw、markRaw ) 详解
我们在写项目中,总会遇到变量的定义,在 Vue2 里面,我们只需要直接定义变量就行,但是在 Vue3 里面,官方为我们推出了定义变量的方式,那么接下来我们来看看,官方给出的函数:reactive、ref、toRef、toRefs。
1. reactive 函数
reactive 会对传入的引用类型进行包裹,创建一个该对象的 Proxy 代理。它是源对象的响应式副本,不等于原始对象。它“深层”转换了源对象的所有嵌套 property,解包并维持其中的任何 ref 引用关系。
用来定义引用类型的响应式数据,参数只能传入引用类型,返回一个具有响应式状态的副本;
使用 reactive 定义的属性可以直接使用,不需要加 .value;
不要直接解构使用 reactive 定义的响应式对象,否则会造成该对象脱离 ref 响应式。需要用 toRefs 将其转化为响应式数据对象,然后再解构返回。
<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
2. ref 函数
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。
ref 能定义任何类型的响应式数据,参数可以传入任意数据类型。对于基本数据类型,ref 通过给 value 属性设置 setter 和 getter 实现数据劫持,但它的原始数据不会发生更改,且性能优于 reactive。而对于引用类型,ref 仍然是通过 reactive 包装实现的;
ref 定义的变量,改变值要 .value,而在 template 中不用写 .value;
<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>
小结:reactive 对比 ref
从定义数据角度对比: 1. ref 用来定义:基本类型数据; 2. reactive 用来定义:对象(或数组)类型(引用类型); 3. 备注:ref 也可以用来定义对象(或数组)类型数据, 它内部会自动通过 reactive 转为代理对象; 从原理角度对比: 1. ref 通过 Object.defineProperty() 的 get 与 set 来实现响应式(数据劫持); 2. reactive 通过使用 Proxy 来实现响应式(数据劫持), 并通过 Reflect 操作源对象内部的数据; 从使用角度对比: 1. ref 定义的数据:操作数据需要 .value,读取数据时模板中直接读取不需要 .value; 2. reactive 定义的数据:操作数据与读取数据:均不需要 .value;
3. toRef 函数
可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
获取数据值的时候需要加 .value;
使用场景:有一个响应式对象数据,但是模版中只需要使用其中一项数据;
使用 toRef 转化后生成的 ref 数据,如果是引用类型数据时,那么它不是原始数据的拷贝,而是原始数据的引用,改变它的数据也会同时改变原始数据;
<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>
通过案列对比下 ref 和 toRef:
<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 未发生了改变。
ref 创建一个响应式对象,如果传入参数是对象,那么与对象所有嵌套属性都维持数据响应。它的作用是 data 选项 般的存在,即组件内部状态。ref 值改变会触发页面渲染,同时能作为 props 或 事件参数 进行组件通信;
toRef 是对传入对象指定属性的响应式绑定,值改变不会更新视图。因此用它创建的变量不作用于模版渲染,而是用来接收诸如 props 的引用式传递;
当要将 prop 的某个 ref (即用 ref 包装的属性) 传递给复合函数时,toRef 很有用:
export default { setup(props) { useSomeFeature(toRef(props, 'foo')) } }
即使源 property 不存在,toRef 也会返回一个可用的 ref。这使得它在使用可选 prop 时特别有用,因为可选 prop 并不会被 toRefs 处理。
4. toRefs 函数
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。
toRefs 常用于 ES6 的解构赋值操作。但是,对一个响应式对象直接解构时,解构后的数据将不再有响应式,而使用 toRefs 可以方便解决这个问题;
获取数据值的时候需要加 .value;
使用 toRefs 转化后生成的 ref 数据如果是引用类型数据时,那么它不是原始数据的拷贝,而是原始数据的引用,改变它的数据也会同时改变原始数据;
其作用和 toRef 类似,只不过 toRef 是对一个个属性手动赋值,而 toRefs 是自动解构赋值;
使用场景:剥离响应式对象(解构|展开),想使用响应式对象中的多个或者所有属性做为响应式数据;
<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 } } }
5. 其他
isReactive
检查对象是否是由 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>
isRef
检查值是否为一个 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>
unref
如果参数是一个 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>
isProxy
检查对象是否是由 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>
总结
toRef、toRefs 用于处理 组件/函数 传递的响应式数据,如:在接收父组件 props 时 / 或 composables 组合式函数返回数据时建立起某些属性的响应式引用;
通过 ref 包装的属性在 setup 函数内都需要通过 .value 去访问它值 ( template 模版内不用 )。因此,ref、toRef 创建的变量值都需要用变量 .value 读取。reactive 则不用,因为会自动解包分配给它的 ref。
至于 toRefs,如果是解构赋值,如: const { state1, state2 } = toRefs(props),值需要这样获取:state1.value.count;
若整体赋给一个变量,如:const state = toRefs(props),则是 state.state1.value。
只有 toRefs 可以解构;
以上四种方式声明的变量在通过 props 或 事件 传递时,均会维持其响应性;