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

相关推荐
子兮曰4 小时前
async/await高级模式:async迭代器、错误边界与并发控制
前端·javascript·github
恋猫de小郭4 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
GIS之路7 小时前
ArcGIS Pro 中的 Notebooks 入门
前端
IT_陈寒8 小时前
React状态管理终极对决:Redux vs Context API谁更胜一筹?
前端·人工智能·后端
lemon_yyds8 小时前
《vue 2 升级vue3 父组件 子组件 传值: value 和 v-model
vue.js
Kagol9 小时前
TinyVue 支持 Skills 啦!现在你可以让 AI 使用 TinyVue 组件搭建项目
前端·agent·ai编程
柳杉9 小时前
从零打造 AI 全球趋势监测大屏
前端·javascript·aigc
simple_lau9 小时前
Cursor配置MasterGo MCP:一键读取设计稿生成高还原度前端代码
前端·javascript·vue.js
睡不着先生9 小时前
如何设计一个真正可扩展的表单生成器?
前端·javascript·vue.js
天蓝色的鱼鱼9 小时前
模块化与组件化:90%的前端开发者都没搞懂的本质区别
前端·架构·代码规范