当前位置: 首页 > news >正文

vue3中的ref与reactive的区别

目录

    • 1、两者的区别
      • 底层实现
      • 响应式引用与响应式对象
    • 2、用法
    • 3、vue3中声明的数组/对象
      • 3.1 通过reactive 声明的Array/Object,给它重新分配一个全新的对象时,会出错、或失去响应式效果
      • 3.2 解决方案
    • 4、cosnt 说明
    • 5、Proxy 与 defineProperty
    • ref 浅层响应式问题的解决方案
    • 总结
      • `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,如果重新赋值,等同于重新赋值了一个全新的引用,那么之前绑定的响应关系就丢了;






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

相关文章:

  • unity3d:GameFramework+xLua+Protobuf+lua-protobuf,生成.cs,.pb工具流
  • 自然语言处理:第三十三章FILCO:过滤内容的RAG
  • 【OpenHarmony】ArkTS 语法基础 ⑤ ( ArkTS 状态管理 | @State 装饰器定义状态数据 | 使用状态数据渲染组件 )
  • 程序员搞副业一些会用到的工具
  • SpringBoot+Vue图书管理系统(前后端分离)
  • 配网终端通讯管理板,稳控装置通讯管理卡,铁路信号通讯管理卡
  • loading组件封装原理
  • 关于xilinx srio ip复位问题
  • 【复现】含能量路由器的交直流混合配电网潮流计算
  • Ubuntu22.04下源码编译安装pythonocc-7.8
  • 五分钟上手IoT小程序
  • Java基础 - 日期时间(Calendar)
  • Docker面试整理-什么是Docker Compose?
  • JimuReport 积木报表 v1.7.52 版本发布,免费的低代码报表
  • 2024050702-重学 Java 设计模式《实战状态模式》
  • Javascript编码规范
  • leetcode386. Lexicographical Numbers
  • php中curl和soap方式请求服务超时问题
  • Python 反序列化安全问题(二)
  • RxJS: 简单入门
  • spring boot下thymeleaf全局静态变量配置
  • Vue ES6 Jade Scss Webpack Gulp
  • 山寨一个 Promise
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 详解移动APP与web APP的区别
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • 06-01 点餐小程序前台界面搭建
  • Mac 上flink的安装与启动
  • 阿里云移动端播放器高级功能介绍
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • # 计算机视觉入门
  • $(function(){})与(function($){....})(jQuery)的区别
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (六)vue-router+UI组件库
  • (三)mysql_MYSQL(三)
  • (三十)Flask之wtforms库【剖析源码上篇】
  • (转)Windows2003安全设置/维护
  • .NET CF命令行调试器MDbg入门(一)
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .Net 代码性能 - (1)
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • .Net组件程序设计之线程、并发管理(一)
  • @在php中起什么作用?
  • []常用AT命令解释()
  • [2019.2.28]BZOJ4033 [HAOI2015]树上染色
  • [CDOJ 838]母仪天下 【线段树手速练习 15分钟内敲完算合格】
  • [Cloud Networking] Layer 2
  • [Flex] PopUpButton系列 —— 控制弹出菜单的透明度、可用、可选择状态
  • [JavaWeb]—Spring入门
  • [LeetCode] 178. 分数排名
  • [LeetCode]Balanced Binary Tree
  • [LeetCode]—Roman to Integer 罗马数字转阿拉伯数字
  • [Linux]如何理解kernel、shell、bash
  • [MySQL]数据库基础