vue3 响应式之ref、reative、toRef和toRefs
基于对vue的响应式系统的理解,本文浅谈一下reactive、ref、toRef和toRefs的区别与使用,有错请大神指出!!!
reactive(深层次的响应,会影响所有嵌套的属性)
- 作用:只能定义引用数据类型的响应式数据
- 语法:const 变量 = reactive(初始值)
- 获取/设置:变量.属性(模板中使用直接属性名)
- 应用场景:适用于需要监听对象属性变化的场景,如表单数据、状态管理等。
<template>
<p v-for: 'item in tableData'>{{ item }}</p>
// 在vue中,响应式数据的属性优先级比普通变量即结构后的变量高,若两者同名则优先响应式数据的属性,所以渲染的是响应式数据的属性即{{required}}是data1中的required属性非let {required}、{{name}}是data2中的name属性而非let {{name}}。
<p>{{ required }}</p>
<p>{{ name }}</p>
<p>{{ info }}</p>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
// 一维数组是一个特殊的对象,对于数组而言,其属性是自动生成的,就是其下标。
// 所以reactive([1, 2, 3])等同于reactive({'0': 1, '1': 2, '2': 3})
const tableData = reactive([1, 2, 3])
const data1 = reactive({
required: true
})
const data2 = reactive({
name: 'haha',
info: {
city: guangzhou
age: 18
}
})
let { required } = data1
let { name, info } = data2
onMounted(() => {
// 直接赋值
tableData = [1, 1, 1]
// 解构
required = false
name = 'heihei'
info.city = 'beijing'
info.age = 20
console.log(tableData, required, name, info)
})
</script>
解构赋值:
定义:解构赋值会将一个响应式对象的属性值拷贝到一个普通对象中,而不会将整个响应式对象转换为普通对象。
语法:let/const { 属性名… } = 响应式对象(属性名要与响应式对象中的属性名相一致)
应用:
1、普通引用型数据(只有一位属性的引用型数据):解构后变量没有响应式
2、复杂引用型数据(有嵌套对象):解构后一级属性没有响应式,二级属性以后有响应式
以上述代码为例:
疑问:
从结果可以看出,tableData、required和name的值是改变了但其界面确并未改变,而info里的值变了界面也跟着发生变化,这是为什么呢?
结论:
当引用型数据只有一级属性时即其属性均是基本类型数据,直接赋值和解构都会“丢失”数据的响应式(非真正的丢失,是被普通对象取代,其是非响应的)。
分析:
1、tableData是指针,其直接指向一个新对象[1, 1, 1],这个新对象未经过reactive()处理是普通对象。而响应式对象[1, 2, 3]里属性值其实并未发生改变,自然不会触发界面改变。tableData的值之所以变了是因为其指向的对象变了自然值就会发生改变。
2、根据上述提示,data1是只有普通引用型数据,解构后变量required没有响应式。data2是复杂引用型数据,解构后name是一级属性所以没有响应式;info是二级属性,解构后仍具有响应式,所以修改其属性值后原始值和页面也会相应发生变化。
对响应式对象请谨慎使用解构方法!!!
那么对于只有一级属性的响应对象,该如何正确处理呢?
1、把reactive改成ref(ref([1, 2, 3])=reactive({ value: [1, 2, 3] }))
2、把reactive再封装一层,作为对象的属性
3、使用Object.assign()(原对象需通过reative()变成响应式对象)
其实都是将只有一级属性的响应式对象变成复杂的响应式对象(内嵌套对象),然后通过属性修改其值。
改进代码如下:
<script setup>
import { ref, reactive, onMounted } from 'vue'
const title = ref('我是标题')
// 方法一:把reactive改成ref
let tableData1 = ref([1, 2, 3])
// 方法二:把reactive再封装一层,作为对象的属性
let tableData2 = reactive({
msg: [] // 若是对象则为msg: {}
})
// 方法三:使用Object.assign()
let data = reactive({})
onMounted(() => {
// 续方法一:
tableData1.value = [1, 1, 1]
// 续方法二:
tableData2.msg = [1, 1, 1]
// 续方法三:
let tableData3 = Object.assign(data, {[1,1,1]})
})
</script>
ref
- 作用:一般用来定义一个基本类型的响应式数据。ref本质其实也是一个reactive,代码中let n = ref(0)等同于const n = reactive({ value: 0 })
- 语法:let 变量 = ref(初始值)
- 获取/设置:变量.value(模板中使用不需要.value)
- 应用场景:适用于需要监听基本类型数据变化的场景,如计数器、开关等。
<script>
import { ref } from 'vue'
// 错误写法:const n = ref(0)
let n = ref(0)
const setData = () => {
n.value++
}
onMounted(() => {
console.log(n.value)
})
</script>
<template>
<button @click='setData'>我要改变值</button>
<li>{{ n }}</li>
</template>
toRef(与ref/reactive搭配使用)
- 作用:用来给抽离响应式对象中的某一个属性,并把该属性包裹成ref对象,使其和原对象产生链接
- 语法:let 变量 = toRef(响应式变量, 属性名)
- 获取/设置:变量.value(模板中使用不需要.value)
- 应用场景:通过ref/reactive与toRef/toRefd搭配使用,让子组件直接修改父组件的值(这样除了视图会更新,原始值也能修改)
// 父组件
<template>
<p>{{ data }}</p>
<Son :data="data"/>
</template>
<script setup>
import { ref } from "vue";
let data = ref('hello')
setTimeout(() => {
data.value = 'how are you doing'
}, 2000)
</script>
// 子组件
<template>
<div>{{ msg }}</div>
<div>{{ data }}</div>
</template>
<script setup>
import { ref, toRefs, toRef } from "vue";
// 接受来自父组件的传参
const props = defineProps({
data: String,
});
// 错误写法:const msg = ref(props.data);(除非父组件上的data是引用型数据而非基本类型数据)
// 方法1:
const msg = toRef(props, 'data');
// 方法2:
const { data } = toRefs(props);
</script>
虽然父组件data是响应式对象,但是子组件用于接受父组件的props对象是个普通对象。其接收数据过程有点像解构赋值,只是父组件的值变化,props对象就会解构一次。所以msg改变了,data的值也不会发生改变。需要使用toref将普通对象的属性链接原对象的属性而非拷贝值,这样子组件更改值以后父组件也相应变化。
toRefs(与ref/reactive搭配使用)
- 作用:拷贝响应式对象的属性到一个普通对象中,然后普通对象的每个属性都是指向原始对象相应属性的 ref,两者保持引用关系(toRef的升级版)
- 语法:let 变量名 = toRefs(响应式变量)
- 获取/设置:变量.属性.value(模板中使用变量.属性)
toRef和toRefs单独使用没有什么意义,单独使用只会修改对象的属性值并不会导致页面发生变化。
响应式与props比较
props是单向数据传递,只从父组件到子组件并且子组件不能直接修改父组件的数据。使用props传递数据时,父组件的值改变,子组件会相应地发生变化。但是这并不意味着父组件的值子组件就会立即改变,要等下一次组件更新周期才能更新,或者可以使用 watch 监听父组件的值变化并在回调函数中进行相应的操作。而响应式是实时更新的。
新一代开源开发者平台 GitCode,通过集成代码托管服务、代码仓库以及可信赖的开源组件库,让开发者可以在云端进行代码托管和开发。旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。
更多推荐



所有评论(0)