vue3 响应式之ref、reative、toRef和toRefs

基于对vue的响应式系统的理解,本文浅谈一下reactive、ref、toRef和toRefs的区别与使用,有错请大神指出!!!

文章目录

  • reactive(深层次的响应,会影响所有嵌套的属性)
  • ref
  • toRef(与ref/reactive搭配使用)
  • toRefs(与ref/reactive搭配使用)
  • 响应式与props比较

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 监听父组件的值变化并在回调函数中进行相应的操作。而响应式是实时更新的。

你可能感兴趣的:(javascript,vue.js,前端)