vue3中的ref与reactive的区别

1、两者的区别

  • ref:通常用于定义一个响应式引用,例如Number、String、Boolean、Object、Array等
    1. 可以用于定义一个基本数据类型、或者引用数据类型的响应式引用,返回的是一个带有.value属性的对象,但它的.value是一个 Proxy对象,这使得 Vue 也能够正确追踪和响应这些引用的变化
    2. 更简单直观,通过.value来访问和修改值,代码量少,易于理解
    3. 如果是基本类型(单一数据值),ref 也会将它转换成响应式数据,便于监听数据变化
  • reactive:用于定义一个的响应式对象,例如Array、Object等;
    1. 仅用于定义一个引用类型的响应式对象,返回的是深度响应的Proxy对象,对象的任何数据发生变化时(增删改)都会被监测到
    2. 必须是不需要重新分配一个全新的对象的对象
      若重新赋值,会报错或造成响应式丢失,建议使用 ref,详细解释看下方的 vue3中声明数组
  • 无论是ref、reactive最终返回的值都可以监听追踪到值的变化

  • reactive仅用于定义引用数据类型,这是因为底层逻辑用的是 Proxy 实现的,Proxy 不适用 基本数据类型
    尽量不要用 reactive 定义基本类型,有警告错误
    在这里插入图片描述

  • 简单解释一下“重新分配一个全新的对象”,用 ref更合适的原因:
    ref 返回的是响应式引用,在修改值时,用的是.value,而不是直接修改整个ref
    reactive 返回的是响应式对象,在修改值时,是直接赋值(state={}),等同于改掉了整个对象

底层实现

ref会创建一个含有.value属性的对象,Vue 会拦截对 .value 的访问和修改操作,确保在 .value 变化时触发响应式系统的更新。
reactive 主要用于处理复杂对象和数组的响应性。它返回的是一个代理对象,通过 Proxy 拦截对该对象属性的访问和修改,从而实现响应式。

响应式引用与响应式对象

响应式引用

  1. 引用语义:ref返回一个对象,对象中的.value属性是响应式的,我们通过引用.value 来读取和修改值;
  2. 原始值的响应性:读取和修改原始值,也就是读取、修改、重新赋值.value,这并不会改变 ref 对象本身的引用,因此响应性不会丢失;
  3. 在处理值时,ref 会对其进行进行浅层包装,使得可以追踪对象引用的变化,但不会深层追踪对象内部属性的变化。所以通过 ref 声明的 层级太多的对象,可能不会深层追踪对象内部属性的变化,也就是说监测不到所有内部属性的变化。【目前笔者还未测试出来】

响应式对象

  1. reactive 返回一个 proxy 响应式代理对象,只能修改它的属性,不能重新赋值;
  2. 可以追踪其内部所有属性的变化,当属性值被修改时,视图也会自动更新



2、用法

  1. setup() 中使用
    <template>
    	<div>{{ count }} </div>
    	<div>{{ state.age}} </div>
    	<button @click="changeCount">修改count</button>
    	<button @click="changeAge">修改Age</button>
    </template>
    <script>
    	import { ref, reactive } from 'vue';
    	export default {
    	  setup() {
    	    const count = ref(0);
    	    const state = reactive({
    	      name: 'Alice',
    	      age: 30
    	    });
    	    function changeCount() {
    	    	count.value++;
    	    }
    	    function changeAge() {
        		state.age++;
    	    }
    	    return {
    	      count,
    	      state,
    	      changeCount, // 将方法暴露出去
    	      changeAge // 将方法暴露出去
    	    };
    	    
    	  }
    	};
    </script>
    
  2. <script setup> 中使用
    <template>
    	<div>{{ count }} </div>
    	<div>{{ state.age}} </div>
    	<button @click="changeCount">修改count</button>
    	<button @click="changeAge">修改Age</button>
    </template>
    <script setup>
    import { ref, reactive } from 'vue';
    const count = ref(0);
    const state = reactive({
    	name: 'Alice',
    	age: 30
    })
    function changeCount() {
    	count.value++;
    }
      function changeAge() {
       	state.age++;
      }
    </script>
    



3、vue3中声明的数组/对象

在定义数组时 ,建议使用 ref,这是为了避免 对 reactive 定义的值进行重新分配一个全新的对象时,导致的响应式丢失问题。
当然,如果不是重新分配一个全新的对象,推荐用 reactive,具体讲解请看 3.2 解决方案

案例如下:

<template>
  <div>
    refList
  </div>
  <div v-for="(v, i) in refList" :key="i">
    {{ v }}
  </div>
  <button @click="changeRef">修改ref</button>
  <div>
    reactive
  </div>
  <div v-for="(v, i) in reactiveList" :key="i">
    {{ v }}
  </div>
  <button @click="changeReactive">修改reactive</button>
</template>

<script setup>
import { ref, reactive } from 'vue'
const refList = ref([])
const reactiveList = reactive([])
function changeRef() {
  // 改变 refList 
}

function changeReactive() {
  // 改变 reactiveList 
}
</script>

3.1 通过reactive 声明的Array/Object,给它重新分配一个全新的对象时,会出错、或失去响应式效果

  1. const 声明的 Array,重新赋值时会报错【提示它是一个只读常量】,这等同于给它重新分配一个全新的对象const 声明的 Object 也一样
const reactiveList = reactive([1, 2, 3])
function changeReactive() {
  reactiveList = ['01', 1, 2] // 'reactiveList' is constant. eslint[...]
}
//const reactiveList =  reactive({ '0': 1,'1': 2,'2': 3 })
// function changeReactive() {
//   reactiveList = { '0': '01','1': 2,'2': 3 }  // 'reactiveList' is constant. eslint[...]
// }

在这里插入图片描述

  1. let 声明的 Array,重新赋值时可以赋值成功,但它失去了响应式效果,用 let 声明的 Object也一样
let reactiveList = reactive([1, 2, 3])
function changeReactive() {
  reactiveList = ['01', 2, 3]
  console.log(reactiveList) // 输出结果是['01', 2, 3],但页面渲染还是[1, 2, 3]
}
// let reactiveList = reactive({ '0': 1,'1': 2,'2': 3 })
// function changeReactive() {
//   reactiveList = { '0': '01','1': 2,'2': 3 }
//   console.log(reactiveList) // 输出结果是{ '0': '01','1': 2,'2': 3 },但页面渲染还是{ '0': 1,'1': 2,'2': 3 }
// }

3.2 解决方案

  • ref 则是创建一个包含原始值的响应式引用(ref)。当 ref 的值改变时,会触发依赖更新。
  • reactive 创建一个深度响应式对象,对 对象的所有嵌套属性进行响应式处理,说简单点,就是只对它进行修改,不重新赋值

方法一:ref 声明 Array,重新分配一个新对象时,不会失去响应,Object也一样

const refList = ref([1, 2, 3])
function changeReactive() {
  refList.value  = ['01', 2, 3]
  console.log(refList.value) // 输出结果是['01', 2, 3],页面渲染也是['01', 2, 3]
}

const refList = ref({ '0': 1,'1': 2,'2': 3 })
function changeRef() {
  refList.value = { '0': '01','1': 2,'2': 3 }
  console.log(refList.value) // 输出结果是{ '0': '01','1': 2,'2': 3 },页面渲染也是{ '0': '01','1': 2,'2': 3 }
}

方法二:用 reactive 声明的Array,修改时使用Array.push、splice等方法,object同理

const reactiveList = reactive([1, 2, 3])
function changeReactive() {
  reactiveList.push(4) // 输出结果是['01', 2, 3, 4],页面渲染也是[1, 2, 3, 4]
  console.log(reactiveList)
}



4、cosnt 说明

在 Vue 2 中,使用 const 声明的变量确实是 常量,因为 Vue 2 的响应式系统是基于 Object.defineProperty 实现的,无法追踪 const 变量的重新赋值。
const 声明的变量不可重新赋值,但其引用的对象是可变的,想要改变它,只能改变它内部的属性
let 声明的变量可以重新赋值

但在 Vue 3 中,采用了基于 Proxy 的新响应式系统,const 声明的变量依然可以是响应式的。
在vue3的 setup 函数中,const 声明的变量被称之为 响应式引用响应式对象
所以在vue3中,用const声明一个reactive 对象时,想要改变它,只能改变它内部的属性
如果用 let 声明,虽然可以 对整个对象重新赋值,但这就等同于 给它赋值了一个新的引用,因此之前绑定的响应关系失效了

总结

  • reactive创建响应式对象:修改其属性时是响应式的;
  • const 声明的变量:不可重新赋值,确保引用不变,从而保持响应性;
  • let 声明的变量:可以重新赋值,但重新赋值为新对象,就会失去响应性;
  • const 声明的 reactive 响应式对象,是一个固定引用的响应式对象,使用 const 主要就是为了防止重新赋值,而失去响应性。



5、Proxy 与 defineProperty

reactive方法内部是利用ES6的Proxy API来实现的,这里与Vue2中的defineProperty方法有本质的区别。

  • defineProperty只能单一地监听已有属性的修改或者变化,无法检测到对象属性的新增或删除,而Proxy可以轻松实现;
  • defineProperty无法监听属性值是数组类型的变化,而Proxy可以轻松实现。



ref 浅层响应式问题的解决方案

ref 在处理值时,ref 会对其进行进行浅层包装,使得可以追踪对象引用的变化,但层级太多的对象,可能监测不到所有内部属性的变化。
方法一:结合 toRefs 将 reactive 对象转换为 ref

import { reactive, toRefs } from 'vue';
const state = reactive({
  nested: {
    count: 0
  }
});

// 将 reactive 对象的属性转换为 ref
const { nested } = toRefs(state);
// 修改嵌套属性会触发响应式更新
nested.value.count = 1; // 响应式更新

方法二:用this.$forceUpdate()强制刷新

import { ref } from 'vue';
const state = ref({
  nested: {
    count: 0
  }
});

// 修改嵌套属性会触发响应式更新
nested.value.count = 1; // 响应式更新
this.$forceUpdate();

方法三:手动修改、触发响应

import { ref } from 'vue';

const state = ref({
  nested: {
    count: 0
  }
});

function forceUpdate() {
  state.value = { ...state.value };
}

state.value.nested.count = 1; // 这不会触发响应式更新
forceUpdate(); // 这会触发响应式更新



总结

ref

  1. 用于定义一个响应式引用,可以是基本数据类型、引用数据类型,ref会返回一个带有.value属性的对象,而这个.value就是proxy响应式代理对象
  2. 由于.value就是proxy响应式代理对象,所以在读取、修改、重新赋值时针对 .value,这并不会改变 ref对象本身的引用,因此响应性不会丢失;
  3. 在定义 Array 时,更推荐使用 ref,这是因为不能对 reactive 对象重新定义一个全新的引用
  4. 在处理值时,ref 会对其进行进行浅层包装,使得可以追踪对象引用的变化,但层级太多的对象,可能监测不到所有内部属性的变化。可以用(1)this.$forceUpdate()强制刷新;(2)ref结合reactive;(3)手动修改、触发响应

reactive

  1. 仅用于定义一个固定引用的响应式对象,必须是引用数据类型,reactive 返回的是深度响应的proxy对象,对象的所有属性发生变化时,都会被监测到;
  2. 必须是 不需要重新分配一个全新的引用的对象,这是因为修改、重新赋值都是直接对 reactive,如果重新赋值,等同于重新赋值了一个全新的引用,那么之前绑定的响应关系就丢了;






备注:
如有理解错误的观点,请在评论区留言,接受批评和指导

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值