基于对vue的响应式系统的理解,本文浅谈一下reactive、ref、toRef和toRefs的区别与使用,有错请大神指出!!!
<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>
<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>
// 父组件
<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将普通对象的属性链接原对象的属性而非拷贝值,这样子组件更改值以后父组件也相应变化。
toRef和toRefs单独使用没有什么意义,单独使用只会修改对象的属性值并不会导致页面发生变化。
props是单向数据传递,只从父组件到子组件并且子组件不能直接修改父组件的数据。使用props传递数据时,父组件的值改变,子组件会相应地发生变化。但是这并不意味着父组件的值子组件就会立即改变,要等下一次组件更新周期才能更新,或者可以使用 watch 监听父组件的值变化并在回调函数中进行相应的操作。而响应式是实时更新的。