Vue3 响应式大对比:ref vs reactive,到底该怎么选?

reactive 响应式丢失的排查

最近,在使用vue3开发中遇到一个响应式丢失的场景:对一个用reactive声明的对象通过工具函数处理过后,页面没有同步更新数据,排查了很久,才发现是对对象进行解构和重新赋值后,导致了响应式的丢失,例举如下demo来复现一下上述问题:

ini 复制代码
<script setup lang="ts">
import { reactive, ref } from "vue";

let stateReactive = reactive({
  count: 0,
});

const anotherState = reactive({
  message: "Hello Vue 3!",
});

const res = { ...stateReactive, ...anotherState };

function changeRes() {
  stateReactive.count++;
  anotherState.message = "Hello Vue 3 - Updated!";
}

function changeState() {
  stateReactive = {
    count: 3,
  };
}
</script>

如上述图片所示,无论如何点击按钮,页面中的数据都没有发生改变,可见这两组数据响应式出现了丢失的情况。

reactive响应式丢失场景汇总

一举反三,接下来我来汇总一下reactive会丢失响应式的一些场景,帮助大家避免踩坑。

1. 解构赋值导致丢失

php 复制代码
const state = reactive({ count: 0, user: { name: 'Tom' } })

// ❌ 直接解构会丢失响应式
const { count } = state 
console.log(count) // 0,但不是响应式了

// ✅ 解决:使用 toRefs / toRef
const { count } = toRefs(state)

2. 返回新对象覆盖

ini 复制代码
const state = reactive({ list: [1, 2, 3] })

// ❌ 重新赋值整个对象,响应式丢失
state = { list: [4, 5, 6] }

// ✅ 正确做法:修改属性而不是替换对象
state.list = [4, 5, 6]

3. reactive 包裹的对象被 JSON.parse / JSON.stringify 处理

javascript 复制代码
const state = reactive({ user: { name: 'Tom' } })

// ❌ 转换后失去响应式
const newState = JSON.parse(JSON.stringify(state))

// ✅ 如果需要深拷贝,保留响应式,可以用结构化 clone + reactive
const newState = reactive(structuredClone(state))

4. 数组或对象直接解构/赋值

php 复制代码
const state = reactive({ arr: [1, 2, 3] })

// ❌ 解构数组丢失响应式
const arr = state.arr 
arr.push(4) // 不会触发视图更新

// ✅ 使用 toRef 或者始终通过 state.arr 修改
const arr = toRef(state, 'arr')

5. 使用浅拷贝

xml 复制代码
<script setup>
const state = reactive({ user: { name: 'Tom' } })
const userCopy = { ...state.user } // ❌ userCopy 不是响应式
</script>

✅ 正确做法:直接用 state.user,或者 toRefs(state.user)

6. reactive 不能嵌套使用

php 复制代码
const state = reactive({ user: reactive({ name: 'Tom' }) })

// ❌ 内层 reactive 会被 unwrap 掉,丢失预期响应式行为

正确做法:只用一次 reactive,嵌套对象内部会自动递归代理。

ref是否也会丢失响应式

是的,ref也会丢失响应式的,ref 包裹对象 和 reactive 包裹对象的知识表现不同,对ref包裹的对象进行解构依然会出现响应式丢失的情况。

xml 复制代码
<script setup lang="ts">
import { reactive, ref } from "vue";

const stateRef = ref({
  count: 0,
});
  
function changeState() {
  stateReactive = {
    count: 3,
  };
}

const resRef = { ...stateRef.value };

function changeResRef() {
  resRef.count++;
}
</script>

<template>
  <div class="card">
    <button type="button" @click="changeResRef">{{ resRef }}</button>
  </div>
</template>

这里的表现与上述图片一样,点击按钮,按钮中的数据不会发生变化。

ref和reactive丢失响应式场景对比

场景 reactive ref(对象) 具体例子
解构属性 ❌ 丢失 ❌ 丢失 const state = reactive({ count: 0 })const { count } = state // ❌ 非响应式const obj = ref({ count: 0 })const { count } = obj.value // ❌ 非响应式\n
替换整个对象 ❌ 丢失响应式 ✅ 推荐做法 const state = reactive({ a: 1 })state = { a: 2 } // ❌ 丢失响应式const obj = ref({ a: 1 })obj.value = { a: 2 } // ✅ 保持响应式\n
基本类型 ❌ 不支持 ✅ 支持 const num = reactive(0) // ❌ 无效const num = ref(0) // ✅ 正常\n
JSON.parse / 拷贝 ❌ 丢失 ❌ 丢失 const state = reactive({ a: 1 })const copy1 = JSON.parse(JSON.stringify(state))copy1.a = 2 // ❌ 不触发更新const obj = ref({ a: 1 })const copy2 = JSON.parse(JSON.stringify(obj.value))copy2.a = 2
数组解构 ❌ 丢失 ❌ 丢失 const state = reactive({ list: [1,2,3] })const list1 = state.listlist1.push(4)const obj = ref({ list: [1,2,3] })const list2 = obj.value.listlist2.push(4) // ❌ 不触发更新\n
props 传递 可能丢失 可能丢失 // 父组件\n<Child :data="state" />// 子组件const { data } = defineProps<{ data: any }>()// ❌ 解构 data 后丢失响应式\n

避免响应式丢失的方法

常见的解决方案有:

  • 使用 toRef / toRefs 保持解构后的响应式
scss 复制代码
const state = reactive({ count: 0 });
const { count } = toRefs(state);

setInterval(() => {
  state.count++;
  console.log("响应式 count:", count.value); // ✅ 会更新
}, 1000);
  • reactive声明的对象,需要指定修改对象属性,而不是整体覆盖(除非用 ref 包裹)
  • 避免 JSON.stringify / parse 破坏响应式
  • 在组件中不要直接解构 props,配合 toRefs 使用(重要)
xml 复制代码
<!-- Parent.vue -->
<Child :user="user" />

<script setup>
import { reactive } from "vue";
import Child from "./Child.vue";

const user = reactive({ name: "张三" });
</script>

<!-- Child.vue -->
<script setup>
import { toRefs } from "vue";

const props = defineProps({ user: Object });
const { name } = toRefs(props.user);

// ✅ 响应式 name
</script>

ref 与 reactive 的实践建议

使用建议

  • 小数据 / 基本类型 → ref
  • 复杂对象(状态树、多属性) → reactive
  • 需要整体替换的对象 → ref
  • 解构属性时 → 配合 toRefs

参考文献

ref和reactive你必须要知道的使用场景和差异

Vue.js官方文档

相关推荐
胡gh1 小时前
依旧性能优化,如何在浅比较上做文章,memo 满天飞,谁在裸奔?
前端·react.js·面试
在未来等你2 小时前
Redis面试精讲 Day 27:Redis 7.0/8.0新特性深度解析
数据库·redis·缓存·面试
大怪v2 小时前
超赞👍!优秀前端佬的电子布洛芬技术网站!
前端·javascript·vue.js
胡gh2 小时前
你一般用哪些状态管理库?别担心,Zustand和Redux就能说个10分钟
前端·面试·node.js
老华带你飞3 小时前
校园交友|基于SprinBoot+vue的校园交友网站(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·校园交友网站
roamingcode3 小时前
Claude Code NPM 包发布命令
前端·npm·node.js·claude·自定义指令·claude code
码哥DFS3 小时前
NPM模块化总结
前端·javascript
灵感__idea4 小时前
JavaScript高级程序设计(第5版):代码整洁之道
前端·javascript·程序员
唐璜Taro4 小时前
electron进程间通信-IPC通信注册机制
前端·javascript·electron
陪我一起学编程5 小时前
创建Vue项目的不同方式及项目规范化配置
前端·javascript·vue.js·git·elementui·axios·企业规范