vue ref和reactive的区别

下面我把 ref 和 reactive 在 Vue 3(Composition API)中的差异、原理、常见用法与坑都讲清楚——带示例、对比和实战建议,方便你直接拿去用。 核心结论(先看要点)

  • ref:用来包裹 任意值(常用于原始值 Number/String/Boolean),返回 { value } 的响应式容器。对对象也可以用 ref(obj),但它是“以 ref 的形式”持有该对象。模板里会自动解 .value。
  • reactive:接收一个 对象/数组/Map/Set,返回该对象的 Proxy(深度响应式),直接使用属性访问,无需 .value。
  • 选用建议:简单原始值用 ref;结构化对象/集合用 reactive。需要在 Composition API 中解构响应式对象时,使用 toRefs / toRef 保持响应性。

原理简述(简单版)

  • Vue 的响应式基于 Proxy。reactive(obj) 返回对 obj 的代理(proxy),拦截读取/写入来收集依赖并触发更新(深层追踪)。
  • ref(x) 返回一个对象 { value: reactiveWrappedValue }。当 value 被读取/写入时,会进行依赖追踪与触发。对于对象 ref(obj),内部会对对象进行响应式处理(默认深度),但它的外层仍是一个 ref 容器。

代码对比(最小示例)

js
复制代码
import { ref, reactive } from 'vue' // ref 用法(原始值) const count = ref(0) count.value++ // 在 JS 里要用 .value // 在模板中: {{ count }} // 模板会自动解包,不需要 .value // reactive 用法(对象) const state = reactive({ a: 1, b: { c: 2 } }) state.a = 2 // 直接更新属性,响应式生效 // 深层属性 state.b.c 也是响应式的

深入对比:行为与细节

  1. 支持的类型
  • reactive:设计用于对象 ({}/[]/Map/Set)。对非对象使用并不合适(通常直接使用 ref)。
  • ref:任意类型(原始值、对象、数组 等)。
  1. 访问方式
  • ref:在 JS 中必须 .value(模板自动解包)。
  • reactive:直接 state.prop。
  1. 深度(是否递归)
  • reactive:深度响应式(内部所有嵌套对象都会被代理)。
  • ref(obj):外层是 ref,但 .value 的对象也会被转换为响应式(深度),除非使用 shallowRef。
  1. 解构丢失响应性(常见坑) const state = reactive({ a: 1, b: 2 }) const { a } = state // 解构得到的是普通变量,不会响应 // 正确做法: import { toRefs } from 'vue' const { a } = toRefs(state) // a 是 ref,保持响应性 对 ref 解构通常不是问题,因为你通常不会解构 .value。
  2. 判断与转换工具
  • isRef(x)、isReactive(x):判断。
  • unref(x):如果是 ref 则返回 .value,否则返回 x。
  • toRef(obj, 'key') / toRefs(obj):把对象属性转成 ref 便于解构。

与模板的交互

  • 模板会自动 unwrap ref(所以模板中写 {{ count }} 即可)。
  • reactive 在模板中直接使用属性:{{ state.a }}。

与 watch / computed 的配合

  • watch 可以监听 ref 或 reactive(监听整个对象需要传 getter 或 deep: true)。
  • computed 接受函数或 getter,返回 Readonly ref。 示例: watch(() => state.a, (newVal) => { ... }) // 监听 reactive 的某个字段 watch(count, (v) => { ... }) // 监听 ref

特殊 API:shallowRef / shallowReactive / customRef

  • shallowRef:只对 .value 做响应,不会对内部对象做深度转换(适合大对象或第三方不可代理对象)。
  • shallowReactive:只对第一层做响应,内部嵌套对象不再递归代理。
  • customRef:自定义响应行为(节流、防抖等场景)。

与 Object.assign / 解构 / 扩展运算符 的关系(你之前的问题) Object.assign({}, obj1, obj2) 或 { ...obj1 } 会创建一个 新普通对象,拷贝的是 当前值,不会保留原来的 Proxy/响应式连接。举例:

js
复制代码
const a = reactive({ x: 1 }) const merged = Object.assign({}, a) // merged 不是响应式 const spreaded = { ...a } // 也不是响应式

// 如果想得到响应式对象:

js
复制代码
const mergedReactive = reactive(Object.assign({}, a))

如果你想把 reactive 对象的属性合并到另一个 reactive 上,直接修改属性会保留响应性:

js
复制代码
Object.assign(state, other) // 如果 state 和 other 已是 reactive,直接修改 state 的属性会触发响应

常见误区 & 实战建议

  • 原始值优先用 ref(比如 count, isOpen)。
  1. 复杂状态对象/集合用 reactive(例如 user = {profile, settings}、数组数据 list)。
  2. 解构 reactive 时丢失响应性:用 toRefs() 或在模板/函数中直接访问 state.prop。
  3. 想替换整个对象:如果你需要替换整个 state(例如 state = newObj),reactive 不能被重新赋值(通常 const state = reactive(...)),你应把需要替换的字段逐一更新,或用 ref 持有对象然后 objRef.value = newObj(这样替换会生效)。
  4. 性能考虑:对大对象或第三方对象使用 shallowRef/shallowReactive,避免不必要的深度代理开销。
  5. 模板里不用 .value,但在 JS 里要注意区分(或使用 unref / toRefs 帮助转换)。

常见场景举例 场景 A:计数器

js
复制代码
const count = ref(0) count.value++

场景 B:表单对象

js
复制代码
const form = reactive({ name: '', age: 0 }) // 方便:直接 form.name = 'x'

场景 C:需要替换整个对象(例如从接口拿到新对象)

js
复制代码
const userRef = ref({}) // 用 ref 包裹对象,方便整体替换 userRef.value = await fetchUser()

或者: // 如果用 reactive:

js
复制代码
Object.assign(user, await fetchUser()) // 更新属性而不是替换引用

场景 D:组合使用(推荐)

js
复制代码
const state = reactive({ items: [] }) const loading = ref(false) // 清晰:state 用于结构化数据,loading 用于原始布尔

快速 API 备忘(常用函数)

  • ref(value), reactive(obj), readonly(x)
  • isRef(x), isReactive(x), unref(x)
  • toRef(obj, key), toRefs(obj)
  • shallowRef, shallowReactive, customRef
  • markRaw(obj):标记对象不被代理
  • proxyRefs(obj):把对象属性的 ref 自动解包(常用于返回 setup 的对象,便于模板使用)

总结(一句话)

  • ref 是值的容器(灵活,常用于原始值及需要整体替换的对象);reactive 是对象的 Proxy(更自然地操作对象属性)。了解两者的访问方式(.value vs 直接属性)、解构坑(toRefs)和浅/深区别,就能在实际项目中正确选择。

toRef 和 toRefs的区别 toRef 和 toRefs 是 Vue 3 响应式系统中非常关键的两个工具函数,主要用于 保持响应性 —— 特别是在你从 reactive 对象中“解构属性”时。 下面我会详细讲它们的 区别、用途、源码原理,再配上清晰的图和示例。


🧩 一句话区别总结 暂时无法在飞书文档外展示此内容


🧠 为什么要有它们? 当你从一个 reactive 对象中“解构属性”时,响应性会丢失。 ❌ 普通解构会丢失响应性

js
复制代码
const state = reactive({ count: 0, name: '小明' }) const { count } = state // ❌ count 不是响应式的 count++ // 修改不会触发视图更新

原因: 解构后得到的是普通值,而不是响应式引用(Vue 的 Proxy 失效了)。


✅ 解决办法:toRef

js
复制代码
import { reactive, toRef } from 'vue' const state = reactive({ count: 0 }) const count = toRef(state, 'count') console.log(count.value) // 0 count.value++ console.log(state.count) // 1 ✅ 保持同步

✨ 特点:

  • toRef(state, 'count') 返回一个 ref 对象,其 .value 始终指向 state.count。
  • 它 不会创建新的值或副本,只是创建了一个“引用镜像”。
  • 常用于“传递单个属性”给子组件或 watch。

✅ 多属性情况:toRefs

js
复制代码
import { reactive, toRefs } from 'vue' const state = reactive({count: 0,name: '小明' }) // 把所有属性都转成 ref const { count, name } = toRefs(state) count.value++ name.value = '小东' console.log(state.count) // 1 ✅ console.log(state.name) // 小东 ✅ ✨ 特点: - 它会遍历对象的所有 key,执行 toRef(state, key)。 - 返回的对象与原对象 结构相同,但每个属性都是 ref。 - 常用于在 setup() 里返回响应式状态给模板: return { ...toRefs(state) // 模板中直接用 {{ count }} {{ name }} }

⚙️ 底层关系图(简化版) ┌────────────────────────┐ │ reactive对象 state │ │ { │ │ count: 0, │ │ name: "小明" │ │ } │ └────────────┬───────────┘ │ ├── toRef(state, "count") → Ref对象 { value ↔ state.count } │ └── toRefs(state) → { count: Ref, name: Ref }


🧩 常见使用场景对比 ✅ 场景 1:只需要一个属性(推荐用 toRef)

js
复制代码
const user = reactive({ name: 'Tom', age: 18 }) const age = toRef(user, 'age') // watch 单个字段 watch(age, (v) => console.log('年龄变化', v))

✅ 场景 2:需要在模板中用多个属性(推荐用 toRefs)

js
复制代码
const user = reactive({ name: 'Tom', age: 18 }) return { ...toRefs(user) // 模板中直接用 {{ name }} {{ age }} } ---

✅ 场景 3:嵌套响应式对象

js
复制代码
const state = reactive({user: { name: 'Tom', age: 18 } }) const user = toRef(state, 'user') // user 是一个 ref,指向整个对象 user.value.age++ // 更新响应式
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP