人人都会AI编程

深层响应式与浅层响应式原理

更新时间:2026-07-09

Vue 3 的响应式系统默认是深层的,这意味着当你用 reactive 包裹一个对象时,对象内部的所有嵌套属性都会被递归地转换为响应式数据。同时,Vue 也提供了 shallowReactiveshallowRef 来创建浅层响应式,只在第一层属性上建立响应式关联。

理解这两者的区别和原理,能帮你更精准地控制响应式的边界,避免不必要的性能开销或潜在的状态不一致问题。


深层响应式(默认行为)

当你调用 reactive(obj) 时,Vue 内部会这么做:

  1. obj 创建一个 Proxy 代理,拦截对它的所有读写操作。
  2. 当读取某个属性(如 obj.nested)时,Vue 会检查该属性的值是否是一个对象。如果是,递归地调用 reactive 将其也转换为响应式对象。
  3. 这个递归过程会一直深入到最内层,直到所有嵌套对象都被代理。

同理,ref 在包裹一个对象时,内部也会调用 reactive 进行深层转换。

示例:

import { reactive } from 'vue'

const state = reactive({
  user: {
    name: 'Alice',
    address: {
      city: '北京'
    }
  }
})

// 深层响应式意味着你可以直接修改深层属性,视图会自动更新
state.user.address.city = '上海' // ✅ 触发更新
state.user.name = 'Bob'          // ✅ 触发更新

从原理上看,深层响应式的“递归”是惰性的——只有当你真正访问到一个嵌套对象时,它才会被转换为响应式。不是创建响应式对象时就一口气递归所有层级,而是按需触发。


浅层响应式:shallowReactiveshallowRef

在某些场景下,你并不需要所有嵌套属性都变成响应式的。例如:

  • 数据结构非常深且庞大,完全递归会带来不必要的性能开销。
  • 你希望某些嵌套对象保持非响应式状态(比如第三方库的实例、复杂的数据快照等),只在顶层属性变化时触发更新。

Vue 为此提供了浅层响应式 API

1. shallowReactive
只对对象的第一层属性进行代理。深层嵌套的对象不会被自动转换为响应式,修改它们的属性不会触发任何更新。

import { shallowReactive } from 'vue'

const state = shallowReactive({
  count: 0,
  nested: {
    deep: 1
  }
})

state.count++                 // ✅ 触发更新(第一层属性)
state.nested.deep++           // ❌ 不会触发更新(深层属性未被代理)
state.nested = { deep: 3 }    // ✅ 触发更新(第一层属性整体被替换)

内部实现上,shallowReactive 创建的 Proxy 在拦截 get 时,不再递归调用 reactive,而是直接返回原始值。因此深层属性的读写不会走到响应式系统的依赖追踪和派发更新流程。

2. shallowRef
普通的 ref.value 的赋值会触发深层响应式转换(如果值是对象)。而 shallowRef.value 变化时,只有值本身的引用变化才会触发更新,对象内部的属性修改不会被追踪。

import { shallowRef } from 'vue'

const data = shallowRef({ name: 'Alice' })

data.value = { name: 'Bob' }  // ✅ 触发更新(.value 整体替换)

// 修改内部属性不会触发更新
data.value.name = 'Charlie'   // ❌ 视图不更新

内部原理很简单:shallowRef 不会对 .value 进行 reactive 包装,因此一个对象被放进 shallowRef 后,它就是一个普通的 JavaScript 对象,没有任何响应式能力。只有当 .value 被重新赋值为一个新对象时,才会触发依赖更新。


如何选择:深层 vs 浅层

| 特性 | 深层响应式 (reactive/ref) | 浅层响应式 (shallowReactive/shallowRef) |
|------|-------------------------------|--------------------------------------------|
| 嵌套对象代理 | 自动递归代理 | 仅第一层代理 / 仅监听引用变化 |
| 性能开销 | 小对象可忽略;大型深层对象有递归和追踪成本 | 更轻量,适合大型数据结构或不需深层响应的部分 |
| 使用场景 | 绝大多数业务逻辑状态 | 第三方不可变数据、大列表只改引用、与外部系统交互 |

实用建议:

  • 如果你不确定该用哪个,先默认使用深层响应式。Vue 的响应式系统经过了精心优化,普通规模的嵌套对象几乎不会带来可见的性能问题。
  • 当你明确知道某些数据是“只读的”或“完全替换”模式(比如整个表单对象一次性提交,或者从服务端拿到的列表整体替换),可以切换到浅层响应式,既能减小内存占用,也能让意图更清晰。
  • 对于 ref,特别要注意:如果使用 shallowRef 存放一个对象,修改对象内部属性不会触发更新。这在使用第三方可视化库(如 ECharts 实例、地图实例)时非常有用——这些实例往往有复杂的内部状态,你只需要保证实例引用不变,而让库自己去处理内部变化。

深层与浅层响应式并非“谁好谁坏”,而是提供了一种精确控制响应式边界的手段。善用它们能让你的状态管理更符合实际数据流,也更容易排查问题。