Vue3之ref和reactive的使用与区别

本文详细比较了Vue的ref和reactiveAPI在接收值、数据访问、watch监听和类型判断上的差异,以及toRef和toRefs的用途,强调了ref适合状态容器,而reactive用于复杂数据对象的追踪。

   ref() 和 reactive() 都是响应式api,用来跟踪其参数的更改,让组件具有响应性。

1. ref和reactive的区别

1.1 接收值不同

ref

        ref 既可以处理基础数据类型的值,也可以处理复杂数据类型的值。

ref处理复杂数据类型       

        如果给 ref 函数传递了一个复杂数据类型的值,如传递一个对象,那么这个对象就会通过 reactive() 方法转换成具有深层次的响应式对象

        也就是说这种情况下ref 内部本质还是调用了 reactive 方法。

const ref1 = ref(0)            // OK
const ref2 = ref({ count: 0 }) // OK

reactive        

        reactive 仅处理复杂数据类型的值,不允许传递基础类型的值。

        reactive若传入基础类型的值,对该基础类型数据是没有响应式追踪能力的。

const reactive1 = reactive(0)            // NOT OK
const reactive2 = reactive({ count: 0 }) // OK

1.2. 数据访问方式不同

ref

        对于 ref 来说,无论是原始类型还是对象,访问数据都需要通过 .value 的形式进行,更新数据也需要通过 .value 来更新。

        但在<template>中使用ref的值时不需要带上.value。

const ref1 = ref(0) 
console.log(ref1.value)  // 0

const ref2 = ref({ count: 0 })
console.log(ref2.value.count)  // 0

ref1.value = 1
console.log(ref1.value)  // 1
<template>
      <div>{{ ref1 }}</div>
</template>

<script setup>
    import { ref } from 'vue'
    const ref1 = ref(0)
</script>

reactive        

        reactive是 proxy 代理的对象数据,可以直接获取到数据,不必添加 .value。

        看到ref取值和reactive的取值方式不同,可以看出ref本质就是基于reactive的二次包装。

const reactive1 = reactive({ num: 0}) 
console.log(reactive1.num)  // 0

1.3. watch监听方式不同

ref

        可以直接监听 ref 的数据,当 ref 的数据发生变化的时候,就会执行 watch 函数对应的回调。

const ref1 = ref(0)
watch(ref1, () => { 
  console.log('changed!')
})

        当然这里只是原始类型数据,如果是对象的话,需要深度监听 deep: true

const ref1 = ref({num: 1})
watch(ref1, () => { 
    console.log('changed!')
})

// ref1.value.num = 1 
// 执行该语句时并不会触发watch监听,watch 并没有对ref1进行深度监听
// 但注意,此时dom是能更新的,ref会将其转换成 reactive 的形式

// 要想深入监听,只需要加一个对应的参数即可
const ref1 = ref({num: 1})
watch(ref1, () => { 
    console.log('changed!')
}, { deep: true })

reactive

        reactive 因为本质是对象,所以在 watch 的时候本能的会添加 deep 属性。

        vue 对其做了优化watch 监听 reactive 的时候可以不添加 deep 属也能够对其做深度监听。

const reactive1 = reactive({num: 1})
watch(reactive1, () => { 
    console.log('changed!')
})

// reactive1.num = 1
// 触发watch监听

备注

        对比 ref,reactive 会跟踪每个属性的变动。
        这意味着在 reactive 中的每个属性都会被视为独立的 ref ,Vue 会据此确定是否需要更新某些依赖于这些属性的内容
        如果想使用ref 作为状态容器,那么每当一个属性更新时,使用该状态的任何地方都将被更新。这会触发不必要的重新渲染并减慢应用程序的速度。

2. ref、toRef、toRefs的区别

ref

        复制,修改响应式数据不影响以前的数据。

toRef

        引用,修改响应式数据会影响以前的数据。

toRefs

        1. 接收一个对象作为参数,它会遍历对象身上所有属性,然后调用单个toRef。

        2. 将对象的多个属性变成响应式数据,并且要求响应式数据和原始数据关联。

3. 通过typeof判断ref和reactive类型

3.1. ref 类型判断   

        ref 关键字声明一个变量时,变量的类型会被推断为 Ref 类型,无论变量本身是什么类型的值,typeof 判断为Object。
        可以使用 unref 函数来解包得到ref 对象的实际值,再对unref()进行判断。

let num = ref(123)
console.log(typeof num) //Object
console.log(typeof unref(num)) //Number

3.2.reactive类型判断 

        reactive用来声明复杂数据类型时,typeof判断为Object。

let myName = reactive({name:'min'})
console.log(typeof myName) //Object
console.log(typeof myName.name) //String

疑惑

        reactive用来声明基础数据类型时,typeof判断按理说会判断为Object(被reactive包装了)

        但试验发现仍会判断为原本的数据类型

        但总而言之是不推荐用reactive包裹基础数据类型的

let obj = reactive(123)
console.log(typeof obj) //number

4. reactive丢失响应式的问题

        使用时发现,reactive 定义数组会丢失响应式的问题。

        如例所示,此时会发现虽然 onMounted 里对batchMoreOperationList 赋值了,但是页面上的batchMoreOperationList 并未更新。        

let batchMoreOperationList = reactive([])
  
onMounted(() => {
  if (props.activeName !== versionType.EXCLUSIVE && props.activeName !== versionType.PRIVILEG) {
    batchMoreOperationList = batchMoreOperation.filter(item => item.name !== '批量更换机型')
  } else {
    batchMoreOperationList = batchMoreOperation
  }
})

        这是因为 reactive 定义的变量,在直接赋值时会丢失响应式的问题。

        更推荐用 ref 来定义数据会发生变化的复杂数据类型。

// 使用ref来定义
let batchMoreOperationList = ref([])
  
onMounted(() => {
  if (props.activeName !== versionType.EXCLUSIVE && props.activeName !== versionType.PRIVILEG) {
    batchMoreOperationList.value = batchMoreOperation.filter(item => item.name !== '批量更换机型')
  } else {
    batchMoreOperationList.value = batchMoreOperation
  }
})

参考:https://blog.csdn.net/qq_44848480/article/details/134415350

5. 为什么复杂数据类型也常用ref定义

        项目实践中发现,尽管reactive看起来专门处理复杂数据类型,但往往复杂数据类型还是以ref定义偏多。

原因1

        ref 可以灵活地声明基本数据类型or复杂数据类型,不像 reactive 那样受限于只能处理复杂数据类型。

原因2

        ref 定义的复杂数据类型可以重新赋值,赋值后仍保持响应式;

        而reactive定义的复杂数据类型若重新赋值会丢失响应式(见第4条),因此若需要修改元素值,通常通过push、splice等方法来修改。

// 1.ref声明数组
let arr1 = ref([])
let copy1 = arr1
arr1.value = [1, 2, 3]

// 2.reactive声明数组
// (1)直接对数组赋值
let arr2 = reactive([])
let copy2 = arr2
arr2 = [1, 2, 3] //丢失响应式
// (2)修改数组属性
let arr3 = reactive([])
let copy3 = arr3
arr3.push(1, 2, 3)
// 打印结果
console.log('arr1.value', arr1.value)
console.log('copy1.value', copy1.value)
console.log('arr2', arr2)
console.log('copy2', copy2)
console.log('arr3', arr3)
console.log('copy3', copy3)

6. 为什么const声明的复杂数据类型变量仍能修改

        对于基本数据类型,const声明的变量的值是不可改变的。

        对于复杂数据类型,不管是ref还是reactive定义的对象,都可以修改对象的属性值,但不能重新赋值整个对象。

// 1. const声明的基础数据类型变量不可更改
const obj1 = 42
// obj1 = 43 // 不允许,这会抛出错误

// 2. const声明的复杂数据类型变量,可修改属性,不可重新赋值
const obj2 = {
  name: 'csdn'
}
obj2.name = 'not-csdn' // 允许的,只是修改了对象的一个属性
// obj2 = { name: 'not-csdn' } // 不允许,这会抛出错误,因为你试图改变obj的引用

原理

        const并不是让变量的值变得不可变,而是让变量指向的内存地址不可变。

        也就是说,使用const声明的变量不能被重新赋值,但是它所指向的内存中的数据是可以被修改的。使用const后,实际上是确保该变量的引用地址不变,而不是其内容。这也解释了为何不可直接赋值(改变变量的引用地址),但可以更改对象属性值(改变引用地址里的数据)。

7. shallowRef是什么,和ref、reactive的区别

        推荐阅读该文,不在此赘述,:https://juejin.cn/post/7291468863396560957

总结

        1. 推荐只用ref声明普通响应式变量即可,不要refreactive混用。

        2. 推荐只有当第三方库(如 echarts)的组件操作对象需要有响应性时,才使用shallowRef

        3. 推荐只有当使用组合式函数需要命名空间时,才使用reactive

组合式函数:组合式函数 | Vue.js

        

        

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值