Vue 3 reactive 和 ref 区别及 失去响应性问题

在 Vue 3 中,reactive 和 ref 是实现响应式数据的两个核心 API,它们的设计目标和使用场景有所不同。以下是两者的详细对比:

1. 基本定义与核心功能

特性reactiveref
作用创建对象类型的响应式代理(对象、数组、Map 等)创建一个响应式的引用,可用于基本类型或对象
返回值原始对象的代理(Proxy包含 value 属性的响应式对象(如 { value: xxx }
响应式原理基于对象的属性劫持(Proxy + Reflect基于 getter/setter 劫持,基本类型和对象统一处理
解包机制无(直接访问属性)对象会自动解包为 reactive,基本类型需通过 .value 访问

2. 详细区别

(1) 适用数据类型
  • reactive
    仅用于对象类型(包括普通对象、数组、MapSet 等),无法直接处理基本类型(如 stringnumberboolean)。

    javascript

    // 正确:对象类型
    const state = reactive({ count: 0 });
    // 错误:不能直接处理基本类型(会被视为对象属性名)
    const num = reactive(1); // 报错:reactive() expects an object
    
  • ref
    可处理所有类型(基本类型或对象)。对于对象,ref 内部会自动通过 reactive 转为响应式代理(即 “自动解包”)。

    javascript

    // 基本类型
    const count = ref(0);
    // 对象类型(内部自动转为 reactive 代理)
    const obj = ref({ name: 'Vue' });
    
(2) 访问方式
  • reactive
    直接通过属性访问,无需额外语法:

    javascript

    const state = reactive({ count: 0 });
    state.count = 1; // 直接修改属性
    
  • ref

    • 基本类型:需通过 .value 访问和修改:

      javascript

      const count = ref(0);
      count.value = 1; // 修改需通过 .value
      
    • 对象类型:由于自动解包,可直接访问属性(内部已转为 reactive 代理):

      javascript

      const obj = ref({ name: 'Vue' });
      obj.value.name = 'Vue 3'; // 直接修改属性(等价于 reactive 的操作)
      // 或通过解包后访问(模板中无需 .value)
      
(3) 响应式解包规则
  • 在模板中

    • reactive 创建的对象:直接通过属性名访问,无需特殊处理。
    • ref 创建的值:
      • 基本类型:模板中会自动解包,直接使用 {{ count }} 即可(无需 {{ count.value }})。
      • 对象类型:同样自动解包,等价于 reactive,直接访问属性(如 {{ obj.name }})。
  • 在 JavaScript 中

    • 若从 ref 中解构属性,需使用 toRefs 或手动保留 ref 引用,否则会失去响应式:

      javascript

      const obj = ref({ name: 'Vue', age: 3 });
      const { name } = obj.value; // 错误:name 是普通值,失去响应式
      const { name } = toRefs(obj.value); // 正确:通过 toRefs 保持响应式
      
(4) 重新赋值与响应式
  • reactive
    代理对象指向固定,不能直接重新赋值为新对象(否则会失去响应式),需通过修改属性实现更新:

    javascript

    const state = reactive({ count: 0 });
    state = { count: 1 }; // 错误:直接赋值会导致响应式丢失
    state.count = 1; // 正确:修改属性
    
  • ref
    可以重新赋值为新值(基本类型或对象),响应式会自动更新:

    javascript

    const count = ref(0);
    count.value = 1; // 基本类型重新赋值(正确)const obj = ref({ name: 'Vue' });
    obj.value = { name: 'React' }; // 对象重新赋值(正确,内部会重新创建 reactive 代理)
    
(5) 组合式 API 中的使用
  • reactive
    适合定义包含多个属性的对象状态,常用于复杂状态管理:

    javascript

    setup() {const state = reactive({count: 0,user: { name: 'Alice' }});return { state }; // 模板中通过 state.count 访问
    }
    
  • ref
    适合定义单个值(基本类型或对象),或需要在函数间传递的独立状态,且返回时无需嵌套对象:

    javascript

    setup() {const count = ref(0);const user = ref({ name: 'Alice' });return { count, user }; // 模板中直接使用 count 和 user.name
    }
    

3. 典型使用场景

场景reactive 更合适ref 更合适
定义对象 / 数组状态✅(reactive({ a: 1, b: 2 })✅(ref({ a: 1, b: 2 }),自动解包)
定义基本类型状态(如计数)❌(不支持)✅(ref(0)
函数返回单个响应式值❌(需返回对象)✅(直接返回 ref(0),模板自动解包)
解构响应式对象并保持响应式需要配合 toRefsconst { a, b } = toRefs(state)直接解构 ref(如 const { value } = count,但通常无需解构)
动态创建响应式变量需手动构建对象直接使用 ref 包裹任意值

4. 最佳实践

  1. 基本类型用 ref
    无论何时需要响应式的基本类型(如 countisLoading),直接使用 ref

    javascript

    const count = ref(0);
    const isLoading = ref(false);
    
  2. 对象 / 数组用 ref 或 reactive

    • 若状态是单个对象 / 数组,推荐用 ref(统一接口,自动解包):

      javascript

      const user = ref({ name: 'Vue', age: 3 });
      
    • 若需要在一个对象中整合多个状态(如表单数据),可用 reactive

      javascript

      const form = reactive({name: 'Alice',email: 'alice@example.com'
      });
      
  3. 解构时保持响应式
    当从 reactive 对象中解构属性时,必须使用 toRefs 避免失去响应式:

    javascript

    const state = reactive({ a: 0, b: 0 });
    const { a, b } = toRefs(state); // a 和 b 是 ref,保持响应式
    
  4. 避免混合使用导致混乱

    • 尽量统一风格:若状态是对象,要么全用 reactive,要么全用 ref 包裹对象。
    • 优先使用 ref 包裹对象:因为 ref 可以无缝处理基本类型和对象,且返回值更简洁(无需嵌套对象)。

总结

  • ref 是更通用的选择:可以处理所有数据类型,对象会自动解包为 reactive,且在组合式 API 中返回更方便(直接返回独立的 ref 变量)。
  • reactive 适合复杂对象:当需要定义包含多个属性的对象状态,且不想通过 .value 访问时(虽然 ref 包裹对象后也无需 .value,但 reactive 更直接)。

理解两者的核心区别后,可根据具体场景选择最合适的 API,避免响应式失效问题(如之前代码中 reactive 对象直接赋值导致的更新失败,改用 ref 或正确更新属性即可解决)。

====================================================================


当需要整体更新 reactive 对象中的某个嵌套对象(例如从接口获取全新的用户数据并覆盖原有对象),同时又要保持响应式时,关键是不替换整个 reactive 对象的引用,而是通过以下两种方式更新其属性:

方法一:使用 Object.assign 合并新对象(推荐)

适用场景:
  • 已有 reactive 对象(如 state.user 是 reactive 创建的代理对象)。
  • 需要用新对象的属性覆盖或补充原有对象的属性,而非完全替换整个对象的引用。
示例:

javascript

import { reactive } from 'vue';// 初始化:state 是 reactive 对象,user 是嵌套对象
const state = reactive({user: { name: 'Alice', age: 30 }
});// 假设从接口获取新用户数据(普通对象)
const newUser = { name: 'Bob', age: 35, city: 'Beijing' };// 正确写法:将新属性合并到现有 reactive 对象中(保持代理引用)
Object.assign(state.user, newUser);
// 等价于:state.user = { ...state.user, ...newUser }; (但此写法错误,见下方说明)
关键原理:
  • Object.assign(target, source) 会将 source 的属性直接复制到 target(即 state.user 代理对象),不改变 state.user 的引用,因此响应式得以保留。
  • 错误写法state.user = { ...state.user, ...newUser } 会创建一个新的普通对象并赋值给 state.user,导致其失去 reactive 代理,响应式失效。

方法二:重新赋值时保持 reactive 代理(适用于全新对象)

适用场景:
  • 需要完全替换 reactive 对象中的某个嵌套对象(例如 state.user 原本不存在,或需要用全新的对象结构覆盖)。
  • 确保新对象被重新包裹为 reactive 代理,或通过 ref 间接处理(更推荐)。
方式 1:对新对象重新应用 reactive(不推荐,可能导致性能问题)

javascript

// 错误:直接赋值新普通对象(失去响应式)
state.user = newUser; // 正确:对新对象创建 reactive 代理后再赋值(保持响应式)
state.user = reactive(newUser); 
// 注意:此时 state.user 是新的代理对象,原有代理会被丢弃,可能导致不必要的依赖追踪
方式 2:用 ref 包裹对象(更优雅的解决方案)

如果嵌套对象需要频繁整体更新,建议将其定义为 ref,而非直接作为 reactive 的属性:

javascript

import { reactive, ref } from 'vue';// 初始化:用 ref 包裹 user 对象(可整体更新)
const state = reactive({user: ref({ name: 'Alice', age: 30 }) // user 是 ref 对象
});// 从接口获取新用户数据
const newUser = { name: 'Bob', age: 35, city: 'Beijing' };// 正确写法:直接赋值给 ref 的 value(内部自动处理响应式)
state.user.value = newUser; 
// 或先解包再赋值(模板中无需 .value,自动解包)
原理:
  • ref 允许重新赋值 value(包括对象),且会自动对新对象创建 reactive 代理(即 “自动解包”),因此响应式得以保留。
  • 相比直接使用 reactive 嵌套对象,ref 更灵活,适合需要整体替换的场景。

方法三:解构新对象并逐个更新属性(适用于少量属性)

适用场景:
  • 新对象与原对象结构差异较小,只需更新部分属性。
  • 希望保持代码可读性,避免使用 Object.assign
示例:

javascript

// 假设 newUser 包含需要更新的属性
state.user.name = newUser.name;
state.user.age = newUser.age;
state.user.city = newUser.city; // 直接更新每个属性,保持代理引用

核心原则:避免丢失代理引用

无论采用哪种方法,核心是确保 reactive 对象的引用不被替换

  1. 对 reactive 嵌套对象:通过修改其属性或合并新属性(Object.assign),而非重新赋值整个对象。
  2. 对频繁整体更新的对象:改用 ref 包裹(const obj = ref(initialValue)),允许直接赋值新对象到 obj.value,利用 ref 的自动响应式处理。

完整示例对比(错误 vs 正确)

错误写法(失去响应式):

javascript

const state = reactive({ user: { name: 'Alice' } });
// 错误:直接赋值新对象,user 失去 reactive 代理
state.user = { name: 'Bob' }; 
// 视图不会更新,因为 state.user 现在是普通对象
正确写法 1(合并属性):

javascript

const state = reactive({ user: { name: 'Alice' } });
const newUser = { name: 'Bob', age: 30 };
// 正确:合并新属性到现有代理对象
Object.assign(state.user, newUser); 
// 或逐个赋值:state.user.name = 'Bob'; state.user.age = 30;
// 视图会正确更新
正确写法 2(用 ref 包裹对象):

javascript

const state = reactive({ user: ref({ name: 'Alice' }) });
const newUser = { name: 'Bob', age: 30 };
// 正确:赋值给 ref 的 value,内部自动处理响应式
state.user.value = newUser; 
// 视图会正确更新

总结:推荐方案

  1. 优先使用 ref 包裹需要整体更新的对象
    定义时用 ref,更新时直接赋值 obj.value = newObj,简洁且避免响应式丢失问题。

    javascript

    const user = ref({}); // 初始化为 ref 对象
    user.value = await fetchUser(); // 直接赋值新对象,保持响应式
    

  2. 若必须使用 reactive 嵌套对象
    使用 Object.assign 合并新属性,或逐个更新属性,确保不替换代理对象的引用。

    javascript

    Object.assign(state.user, newUser); // 合并属性,保持代理
    

通过以上方法,既能实现整体对象的更新,又能保持 Vue 3 的响应式特性,避免因引用丢失导致的视图不同步问题。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第一节:Vben Admin 最新 v5.0初体验

系列文章目录 基础篇 第一节:Vben Admin介绍和初次运行 第二节:Vben Admin 登录逻辑梳理和对接后端准备 第三节:Vben Admin登录对接后端login接口 第四节:Vben Admin登录对接后端getUserInfo接口 第五节:Vben Admin权…

Nginx部署spa单页面的小bug

没部署过,都是给后端干的,自己尝试部署了一个下午终于成功了 我遇到的最大的bug是进入后只有首页正常显示 其他页面全是404,于是问问问才知道,需要这个 location / { try_files $uri $uri/ /index.html; } 让…

面试算法高频08-动态规划-01

动态规划 递归知识要点 递归代码模板:提供递归代码的标准形式public void recur(int level, int param) ,包含终止条件(if (level> MAX_LEVEL))、当前层逻辑处理(process(level, param))、向下一层递归…

若依框架前后端分离版部署全流程详解(本地+服务器+高级配置)

若依框架前后端分离版部署全流程详解(本地服务器高级配置) 若依(RuoYi)作为一款基于SpringBoot和Vue的权限管理系统,凭借其模块化设计和开箱即用的特性广受开发者欢迎。本文将从本地部署、服务器部署、高级配置三个维…

医疗设备预测性维护合规架构:从法规遵循到技术实现的深度解析

在医疗行业数字化转型加速推进的当下,医疗设备预测性维护已成为提升设备可用性、保障医疗安全的核心技术。然而,该技术的有效落地必须建立在严格的合规框架之上。医疗设备直接关乎患者生命健康,其维护过程涉及医疗法规、数据安全、质量管控等…

LLMs基础学习(七)DeepSeek专题(4)

LLMs基础学习(七)DeepSeek专题(4) 文章目录 LLMs基础学习(七)DeepSeek专题(4)DeepSeek-R1 训练过程的四个阶段具体流程小结 “规则化奖励”具体原因小结 “自我认知”(se…

SQL 速查手册

前言:SQL(Structured Query Language)是用于管理关系型数据库的标准语言,广泛应用于数据查询、更新、定义和管理等操作。本文将为你提供一份详细的 SQL 速查手册,涵盖从基础到高级的各种 SQL 操作,帮助你快…

IDEA 中 Scala 项目远程连接虚拟机 Spark 环境

IDEA 中 Scala 项目远程连接虚拟机 Spark 环境 1. 环境准备 确保虚拟机 Spark 环境正常运行 虚拟机中已安装并启动 Spark记录虚拟机的 IP 地址和 Spark 端口(默认 7077)确保虚拟机防火墙允许相关端口访问 本地 IDEA 环境配置 安装 Scala 插件安装 Spar…

.net core 项目快速接入Coze智能体-开箱即用-全局说明

目录 一、Coze智能体的核心价值 二、开箱即用-效果如下 三 流程与交互设计 为什么要分析意图,而不是全部交由AI处理。 四 接入前的准备工作 五:代码实现----字节Coze 签署 JWT和获取Token .net core 项目快速接入Coze智能体-开箱即用 .net core快…

网店运营精细化突破新路径

内容概要 电商战场越来越卷,单纯靠低价和流量轰炸已经玩不转了。今天想要站稳脚跟,精细化运营才是破局密码——从商品怎么选、用户怎么留,到供应链怎么跑得更快,每个环节都得抠细节。比如用数据给选品“开天眼”,把用…

数据结构学习笔记 :线性表的链式存储详解

目录 单链表 1.1 无头单链表 1.2 有头单链表单向循环链表双链表 3.1 双链表 3.2 双向循环链表总结与对比 一、单链表 1. 无头单链表(Headless Singly Linked List) 定义:链表无头结点,直接由头指针指向第一个数据节点。 特点&…

数据库10(代码相关语句)

while循环 declare avgprice numeric(10,2) set avgprice(select avg(price)from titles) //自定义参数 while avgprice<10 //循环条件 begin update titles set priceprice*1.1 end //循环语句操作&#xff0c;当avgprice<10,所有price都加0.1 case语句 查询authors表…

Redis 下载与安装(Windows版)

一、下载 1、redis官网&#xff1a; https://redis.io/downloads/ 2、Github下载地址&#xff1a; https://github.com/MicrosoftArchive/redis/releases 二、安装 1、打开一个命令窗口&#xff0c;通过 cd 命令进入到你解压的目录 2、输入命令 &#xff0c;启动 Redis&…

在高数据速度下确保信号完整性的 10 个关键策略

随着越来越多的传感器连接到系统&#xff0c;需要快速、可靠和安全地传输更多数据&#xff0c;对带宽和设计复杂性的需求也在增加。优先考虑的是确保从 A 发送到 B 的信号不会失真。 确保信号完整性 对于设计依赖于持续准确数据流的数据密集型应用程序的工程师来说&#xff0c…

NAT、代理服务、内网穿透

NAT、代理服务、内网穿透 1、NAT1.1、NAT过程1.2、NAPT2、内网穿透3、内网打洞3、代理服务器3.1、正向代理3.2、反向代理1、NAT 1.1、NAT过程 之前我们讨论了IPv4协议中IP地址数量不充足的问题。NAT技术是当前解决IP地址不够用的主要手段,是路由器的一个重要功能。 NAT能够将…

利用互斥锁或者利用逻辑过期解决缓存击穿问题

缓存击穿问题概述 缓存击穿是指某个 热点数据缓存过期 时&#xff0c;大量并发请求直接穿透缓存&#xff0c;同时访问数据库&#xff0c;导致数据库压力骤增甚至崩溃。以下是基于 互斥锁 和 逻辑过期 的解决方案&#xff1a; 一、缓存击穿的核心原因 热点数据失效&#xff1a…

Vue3组合式API内核解析:从原子状态到企业级架构

一、组合逻辑原子化设计 1.1 状态管理层级拓扑 1.2 组合单元类型对照表 类型典型实现适用场景复用维度UI逻辑单元useForm/useTable表单/列表交互100%跨项目复用业务逻辑单元useOrderFlow订单流程控制同项目跨模块设备能力单元useGeolocation地理位置获取跨技术栈复用状态管理…

新生宿舍管理系统

收藏关注不迷路&#xff01;&#xff01; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff08;免费咨询指导选题&#xff09;&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;希望帮助更多…

从零上手GUI Guider学习LVGL——Button

视频教程请关注我b站&#xff1a;同学_好好学习&#xff0c;这里只是做相应的笔记文稿 从零上手GUI Guider学习LVGL——Buttton 前言&#xff1a; 首先我们为什么要学习LVGL设计工具呢&#xff1f; 1 降低开发难度 2 提高开发效率 所以我们需要学习一款合适的设计工具 在b站很少…

【AAOS】【源码分析】Car UX Restrictions

AAOS UX的核心理念:安全驾驶是驾驶员的首要责任。汽车制造商和应用程序开发人员的所有设计都必须反映这一优先事项。 AAOS平台允许设备制造商(OEM)对不同驾驶状态下的限制进行定制。 驾驶员分心指南 只有符合Driver Distraction Guidelines的应用才可以在驾驶过程中运行。…