前言在 Vue 3 的 Composition API 中ref和reactive是最核心的两个响应式 API。几乎每个 Vue 3 组件都离不开它们。然而很多开发者在实际使用中仍然困惑ref和reactive到底有什么区别为什么ref要.value而reactive不用到底什么时候用ref什么时候用reactive为什么解构reactive会丢失响应性今天这篇文章我将从为什么需要它们开始到使用方式、底层原理、如何选择再到注意事项带你一次性彻底搞懂ref和reactive。一、为什么需要 ref 和 reactive在 Vue 3 之前Vue 2 的响应式系统基于Object.defineProperty实现。这种方式有两个明显的局限无法监听对象新增/删除属性必须使用Vue.set/Vue.delete无法直接监听数组索引修改arr[0] x不会触发更新更重要的是在 Composition API 中我们需要一种方式让普通的 JavaScript 变量变成响应式的。当数据变化时依赖这些数据的 UI 能够自动更新。ref和reactive就是为此而生的两个核心 API。它们都能让数据变得“响应式”但设计哲学和使用方式截然不同。二、ref值级别的响应式容器2.1 什么是 refref用于创建一个响应式的数据引用。它本质上是一个包装对象——内部包含一个.value属性通过 getter/setter 实现对.value的读写拦截。import{ref}fromvueconstcountref(0)// 实际结构类似于{ value: 0 }console.log(count.value)// 0count.value// 修改值2.2 ref 的核心特点特点说明适用类型任意类型基本类型、对象、数组均可访问方式必须通过.value访问和修改模板中使用自动解包无需.value重新赋值✅ 可以安全地重新赋值保持响应性解构❌ 解构会失去响应性2.3 ref 的底层原理ref的实现相对直观——它创建了一个带有.value属性的对象通过 getter/setter 来追踪依赖和触发更新// 简化版的 ref 实现functionref(value){// 如果传入的是对象内部会用 reactive 包裹if(isObject(value)){valuereactive(value)}return{getvalue(){track()// 收集依赖returnvalue},setvalue(newVal){valuenewValtrigger()// 触发更新}}}关键点在于当包装的是对象时ref内部会使用reactive来确保对象的响应性基本类型通过.value的 getter/setter 实现响应式模板中会自动解包ref所以不用写.value2.4 ref 的使用示例script setup import { ref } from vue // 基本类型 const count ref(0) const message ref(Hello) const isLoading ref(false) // 对象类型ref 也可以包裹对象 const user ref({ name: 张三, age: 25 }) // 修改基本类型 count.value message.value World // 修改对象需要 .value user.value.name 李四 // 重新赋值整个对象——✅ 保持响应性 user.value { name: 王五, age: 30 } /script template !-- 模板中自动解包不需要 .value -- p{{ count }}/p p{{ message }}/p p{{ user.name }}/p /template三、reactive结构级别的响应式代理3.1 什么是 reactivereactive用于将对象包括数组、Map、Set 等整体转换为响应式数据。它返回的是原对象的Proxy 代理对代理对象的任何属性读写都会被拦截。import{reactive}fromvueconststatereactive({count:0,user:{name:张三,age:25}})state.count// 直接修改无需 .valuestate.user.name李四3.2 reactive 的核心特点特点说明适用类型仅对象类型Object、Array、Map、Set 等访问方式直接访问属性无需.value模板中使用直接使用与普通对象无异重新赋值❌ 不能直接重新赋值整个对象否则失去响应性解构❌ 解构会丢失响应性3.3 reactive 的底层原理reactive基于 ES6 的Proxy实现。Proxy 可以拦截对目标对象的所有操作——读取、写入、删除、遍历等// 简化版的 reactive 实现functionreactive(target){if(typeoftarget!object||targetnull){returntarget}returnnewProxy(target,{get(obj,key){track(obj,key)// 收集依赖constresobj[key]// 嵌套对象递归代理returntypeofresobject?reactive(res):res},set(obj,key,value){constresultReflect.set(obj,key,value)trigger(obj,key)// 触发更新returnresult},deleteProperty(obj,key){constresultReflect.deleteProperty(obj,key)trigger(obj,key)// 删除属性也能触发更新returnresult}})}Vue 2 vs Vue 3 响应式对比能力Vue 2 (defineProperty)Vue 3 (Proxy)监听数组索引修改❌✅监听 push/pop⚠️ 重写方法✅ 原生支持监听新增属性❌ 需 Vue.set✅ 自动监听监听删除属性❌ 需 Vue.delete✅ 自动监听性能初始化遍历所有属性惰性代理更高效3.4 reactive 的使用示例script setup import { reactive } from vue // 定义响应式对象 const form reactive({ username: , email: , password: , age: 0 }) // 定义响应式数组 const todoList reactive([ { id: 1, text: 学习 Vue 3, done: false }, { id: 2, text: 写一篇博客, done: false } ]) // 修改属性——直接操作无需 .value form.username andy form.age 25 // 修改数组——直接操作 todoList[0].done true todoList.push({ id: 3, text: 分享给朋友, done: false }) // ⚠️ 错误重新赋值整个对象会丢失响应性 // form { username: new, email: newemail.com } // ❌ 不要这样做 /script template input v-modelform.username / input v-modelform.email / ul li v-foritem in todoList :keyitem.id {{ item.text }} /li /ul /template四、ref vs reactive全景对比对比维度refreactive适用类型任意类型仅对象Object/Array/Map/Set访问方式.value直接访问属性模板中使用自动解包无需.value直接使用重新赋值✅ 可以安全重新赋值❌ 重新赋值会丢失响应性解构❌ 解构后失去响应性❌ 解构后失去响应性底层实现包装对象 .value的 getter/setterProxy 代理整个对象嵌套对象内部自动调用reactive深度代理响应式级别值级别结构级别五、真实项目实战如何选择5.1 选择策略根据官方推荐和社区最佳实践场景推荐方案理由基本类型number/string/booleanrefreactive不支持基本类型单个独立值计数、开关、输入框ref结构简单语义清晰表单数据多个关联字段reactive属性直接访问代码简洁复杂对象/配置项reactive保持结构完整性组合式函数返回值ref更灵活类型统一需要整体替换的数据refreactive重新赋值会丢失响应性不确定用哪个ref更通用更灵活官方倾向Vue 官方文档强烈推荐使用ref()作为声明响应式状态的主要 API。非必要情况下最好避免使用reactive。但在实际开发中两者各有适用场景应根据具体情况选择。5.2 实战案例一计数器 用户表单script setup import { ref, reactive } from vue // ✅ 场景1基本类型 → 用 ref const count ref(0) const isLoading ref(false) const increment () { count.value } // ✅ 场景2表单数据关联性强→ 用 reactive const form reactive({ name: , email: , phone: , age: 0 }) const submitForm () { console.log(提交:, form) } // ✅ 场景3混合使用 const userInfo reactive({ id: 1, name: 张三, // 独立的状态用 ref isEditing: ref(false), permissions: ref([read, write]) }) /script5.3 实战案例二列表管理数组场景对于数组ref和reactive都能用但行为有差异script setup import { ref, reactive } from vue // 方式一用 ref 包裹数组 const list1 ref([1, 2, 3]) // 修改需要 .value list1.value.push(4) list1.value [1, 2, 3, 4, 5] // ✅ 整体替换也没问题 // 方式二用 reactive 包裹数组 const list2 reactive([1, 2, 3]) // 修改直接操作 list2.push(4) // ⚠️ 不能整体替换 // list2 [1, 2, 3, 4, 5] // ❌ 会丢失响应性 // 正确做法清空后重新填充 list2.length 0 list2.push(1, 2, 3, 4, 5) /script建议如果数组需要整体替换如从接口重新获取数据用ref更方便。六、常见陷阱与注意事项6.1 ⚠️ 陷阱一解构 reactive 会丢失响应性这是 Vue 3 开发中最常见的错误之一// ❌ 错误解构 reactive 对象conststatereactive({count:0,name:Vue})const{count,name}state// count 和 name 变成了普通变量count// 不会触发视图更新解决方案使用toRefs将每个属性转为refimport{reactive,toRefs}fromvueconststatereactive({count:0,name:Vue})const{count,name}toRefs(state)// ✅ count 和 name 都是 refcount.value// ✅ 会触发更新6.2 ⚠️ 陷阱二给 reactive 重新赋值// ❌ 错误letstatereactive({count:0})state{count:1}// state 不再是响应式对象// ✅ 正确修改属性而非替换对象state.count1// ✅ 正确如果需要重置使用 Object.assignObject.assign(state,{count:1,name:Vue})6.3 ⚠️ 陷阱三在 reactive 中使用基本类型会报警告// ❌ 错误reactive 不支持基本类型constcountreactive(0)// 控制台警告value cannot be made reactive// ✅ 正确基本类型用 refconstcountref(0)6.4 ⚠️ 陷阱四ref 在 reactive 数组/集合中不会自动解包在 Vue 3 中当ref作为响应式数组或原生集合如Map的元素时不会自动解包.value需要手动访问constcountref(0)constarrreactive([count])console.log(arr[0].value)// 需要 .value而不是 arr[0]6.5 ⚠️ 陷阱五忘记 .value在script中操作ref时必须使用.valueconstcountref(0)// ❌ 错误count// 报错count 是对象不能自增// ✅ 正确count.value而在template中Vue 会自动解包不需要.valuetemplate p{{ count }}/p !-- ✅ 不需要 .value -- /template七、总结维度refreactive核心思想值级别的响应式容器结构级别的响应式代理适用场景基本类型、需要整体替换的数据、不确定的场景复杂对象、表单状态、关联性强的数据结构优点灵活通用、可安全重新赋值、API 一致访问无需.value、代码更简洁、嵌套自动响应缺点需要.value略显繁琐不能重新赋值、解构会丢失响应性最终建议新项目优先用ref更通用、更灵活、不容易踩坑关联性强的对象用reactive表单、配置项等场景代码更简洁不确定时用ref它几乎可以覆盖所有场景牢记.value在script中操作ref时不要忘记不要解构reactive如需解构使用toRefs理解ref和reactive的区别与联系是掌握 Vue 3 Composition API 的关键一步。希望这篇文章能帮你彻底搞清楚这对“响应式双雄”在实际项目中用得更加得心应手。