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,如果重新赋值,等同于重新赋值了一个全新的引用,那么之前绑定的响应关系就丢了;






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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/690573.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构--二叉树(二)

链式二叉树 链式二叉树是链式树集合中的一种&#xff0c;该树的每个根节点最多只有两个孩子节点&#xff0c;我们一般用左右孩子来称呼&#xff0c;在初学链式二叉树时&#xff0c;由于大家对链式二叉树的结构掌握还不够深入&#xff0c;为了降低本章的学习难度及成本&#xff…

pytorch构建模型训练数据集

pytorch构建模型训练数据集 pytorch构建模型训练数据集1.AlexNet:1.1.导入必要的库&#xff1a;1.2.数据预处理和增强&#xff1a;1.3.加载数据集&#xff1a;1.4.划分测试集和训练集&#xff1a;1.5.创建数据加载器&#xff1a;1.6.加载AlexNet模型&#xff1a;1.7.修改模型以…

Oracle EBS AP发票创建会计科目错误:子分类帐日记帐分录未按输入币种进行平衡

系统版本 RDBMS : 12.1.0.2.0 Oracle Applications : 12.2.6 问题症状: 提交“创建会计科目”请求提示错误信息如下: 中文报错: 该子分类帐日记帐分录未按输入币种进行平衡。请检查日记帐分录行中输入的金额。 英文报错:The subledger journal entry does not balance i…

电机专用32位MCU PY32MD310,Arm® Cortex-M0+内核

PY32MD310是一颗专为电机控制设计的MCU&#xff0c;非常适合用做三相/单相 BLDC/PMSM 的主控芯片。芯片采用了高性能的 32 位 ARM Cortex-M0 内核&#xff0c;QFN32封装。内置最大 64 Kbytes flash 和 8 Kbytes SRAM 存储器&#xff0c;最高48 MHz工作频率&#xff0c;多达 16 …

Suryxin’s ACM退役记

序 我的记忆力很差&#xff0c;经历过的很多事情都已经记不太清了&#xff0c;其中有很多美好回忆也已经消散&#xff0c;我很惋惜没能留存一些照片和声音或是文字供我怀念&#xff0c;这就像《泰坦尼克号》一样&#xff0c;露丝和杰克感人肺腑的爱情故事&#xff0c;最后也仅…

2024年最新的软件测试面试总结(答案+文档)

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 测试技术面试题 1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 参考答案&#xff1a; 兼容测试主要是检查软件在不同的硬件平台、软件平…

[word] 在Word中插入分页符 #经验分享#经验分享#笔记

在Word中插入分页符 在 Word中插入分页符&#xff1f;教大家如何在正文中快速的插入分页符。 ? ? ?1、在正文中选择我们要分页的位置。 ? ? ?2、点击插入&#xff0c;选择分页功能里面的“分页符”功能&#xff0c;即可成功在我们选择的位置进行分页。 ? ? ? ?以上…

PICRUSt2在微生物功能预测分析中的应用解读

谷禾健康 微生物组学研究现已超越微生物群落组成分析得到更广泛的使用。大量的人类微生物组研究证据表明&#xff0c;肠道微生物组的功能变化对炎症和免疫反应的影响起到关键的影响作用。 16S rRNA分析是微生物组研究作为最常用便捷且具有成本效益的测量技术&#xff0c;用于分…

从技术到产品:以客户为中心的产品研发之路

一、引言 在快速发展的商业环境中&#xff0c;产品作为连接企业与市场的桥梁&#xff0c;其重要性不言而喻。从摸着石头过河搞产品&#xff0c;到广泛传播NPDP&#xff08;新产品开发流程&#xff09;理念&#xff0c;产品研发的道路经历了从直觉驱动到系统思维的转变。本文将…

【SpringBoot】SpringBoot整合RabbitMQ消息中间件,实现延迟队列和死信队列

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 一、&#x1f525;死信队列 RabbitMQ的工作模式 死信队列的工作模式 二、&#x1f349;RabbitMQ相关的安装 三、&#x1f34e;SpringBoot引入RabbitMQ 1.引入依赖 2.创建队列和交换器 2.1 变量声明 2.2 创建…

[经验] 昆山教育网(昆山教育网中小学报名) #媒体#职场发展#微信

昆山教育网&#xff08;昆山教育网中小学报名&#xff09; 昆山教育局网站 网站&#xff1a;昆山市教育局 昆山市教育局全面贯彻执行党和国家的教育方针、政策&#xff0c;落实有关教育工作的法律、法规&#xff1b;负责制定本市教育工作的实施意见和措施&#xff0c;并监督…

WEB漏洞服务能提供哪些帮助

在数字化浪潮的推动下&#xff0c;Web应用程序已成为企业展示形象、提供服务、与用户进行交互的重要平台。然而&#xff0c;随着技术的飞速发展&#xff0c;Web应用程序中的安全漏洞也日益显现&#xff0c;成为网络安全的重大隐患。这些漏洞一旦被恶意攻击者利用&#xff0c;可…

C++笔记之一个函数多个返回值的方法、std::pair、std::tuple、std::tie的用法

C++笔记之一个函数多个返回值的方法、std::pair、std::tuple、std::tie的用法 —— 2024-06-08 杭州 code review! 文章目录 C++笔记之一个函数多个返回值的方法、std::pair、std::tuple、std::tie的用法一.从一个函数中获取多个返回值的方法1. 使用结构体或类2. 使用`std::t…

C# MES通信从入门到精通(11)——C#如何使用Json字符串

前言 我们在开发上位机软件的过程中,经常需要和Mes系统进行数据交互,并且最常用的数据格式是Json,本文就是详细介绍Json格式的类型,以及我们在与mes系统进行交互时如何组织Json数据。 1、在C#中如何调用Json 在C#中调用Json相关的对象的话,需要引用Newtonsoft.Json的dl…

三十六篇:未来架构师之道:掌握现代信息系统典型架构

未来架构师之道&#xff1a;掌握现代信息系统典型架构 1. 引言 在企业的数字化转型浪潮中&#xff0c;信息系统架构的角色变得日益重要。它不仅承载了企业的IT战略&#xff0c;更是确保企业在复杂、动态的市场环境中稳定运行的关键。作为信息系统的骨架&#xff0c;一个精心设…

贪心算法学习二

例题一 解法&#xff08;贪⼼&#xff09;&#xff1a; 贪⼼策略&#xff1a; 由于只能交易⼀次&#xff0c;所以对于某⼀个位置 i &#xff0c;要想获得最⼤利润&#xff0c;仅需知道前⾯所有元素的最⼩ 值。然后在最⼩值的位置「买⼊」股票&#xff0c;在当前位置「卖出」…

Spring进阶技巧:利用AOP提前介入的巧妙实践

Spring框架中的面向切面编程&#xff08;AOP&#xff09;是一种强大的机制&#xff0c;它允许开发者在不修改原有代码的情况下&#xff0c;对程序进行横向切面的功能扩展。AOP提供了一种方式&#xff0c;可以在目标Bean的生命周期早期阶段就实施切面逻辑&#xff0c;这为我们在…

JMS VS AMQP

JMS&#xff08;Java Message Service&#xff09;是一个为Java平台设计的API&#xff0c;主要针对Java开发者提供了一套用于企业级消息服务的标准接口。而AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;是一个应用层协议&#xff0c;它提供了一个开放的、标准…

代码随想录算法训练营第十六天| 找树左下角的值、路径总和、 从中序与后序遍历序列构造二叉树

找树左下角的值 题目链接&#xff1a;找树左下角的值 文档讲解&#xff1a;代码随想录 状态&#xff1a;递归没想到中右左的遍历顺序&#xff0c;迭代想出来了 思路&#xff1a;需要找最大深度&#xff0c;然后使用中右左的遍历顺序找最左节点 题解&#xff1a; int res 0;int…

【RK3568】制作Android11开机动画

Android 开机 logo 分为两种&#xff1a;静态显示和动态显示。静态显示就是循环显示一张图片&#xff1b;动态显示就是以特定帧率顺序显示多张图片 1.准备 android logo 图片 Android logo最好是png格式的&#xff0c;因为同一张图片的情况下&#xff0c;png 格式的比 jpg和b…