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官方文档

相关推荐
To_OC1 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic2 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy2 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志2 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble3 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯4 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜5 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司5 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
江华森6 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http
IT_陈寒6 小时前
Redis的KEYS命令把我搞崩溃了,改用SCAN才活过来
前端·人工智能·后端