shallowRef仅提供顶层.value响应性,不处理只读;需组合readonly实现深度只读,或shallowReadonly实现仅顶层只读。

shallowRef 本身不处理只读属性,它也不负责“只读”逻辑。它的核心职责只有一个:创建一个仅顶层 .value 具有响应性的引用,且不对内部值做任何响应式转换(包括嵌套对象、数组等)。
如果你希望一个 shallowRef 所包裹的值同时具备“浅层响应 + 顶层只读”特性,需要组合使用 shallowRef 和 readonly(或 shallowReadonly),而不是指望 shallowRef 自带只读能力。
✅ 正确做法:shallowRef + readonly 组合
import { shallowRef, readonly } from 'vue'
// 1. 先用 shallowRef 创建浅响应引用
const state = shallowRef({ user: { name: 'Alice' }, count: 0 })
// 2. 再用 readonly 包一层,使其顶层(即整个对象)不可赋值,且所有嵌套也只读
const readOnlyState = readonly(state)
// ❌ 这些都会失败(开发环境报错,生产环境静默)
readOnlyState.value.count = 1 // 报错:Cannot assign to read only property
readOnlyState.value.user.name = 'Bob' // 报错:嵌套也受保护(因为 readonly 是深度只读)
// ✅ 但你可以安全地读取
console.log(readOnlyState.value.count) // 0⚠️ 注意:
readonly(state)的作用对象是state这个 ref 本身(即它的.value属性),所以最终得到的是一个「只读的 shallowRef」——顶层不可赋新值、内部也不可改,完全冻结。
✅ 替代方案:shallowRef + shallowReadonly
如果你只要顶层不可赋值,但允许修改内部嵌套属性(比如想保留 state.value.user.name = 'Bob' 的能力),那就用 shallowReadonly:
立即学习“前端免费学习笔记(深入)”;
import { shallowRef, shallowReadonly } from 'vue'
const state = shallowRef({ user: { name: 'Alice' }, count: 0 })
const shallowReadOnlyState = shallowReadonly(state)
// ✅ 允许修改嵌套属性(无响应式,但语法合法)
shallowReadOnlyState.value.user.name = 'Bob' // 不触发更新,但不会报错
// ❌ 禁止替换整个 .value
shallowReadOnlyState.value = { count: 1 } // 报错:Cannot assign to read only property 'value'? 适用场景:你用
shallowRef是为了性能(避免深层响应),又想防止意外覆盖整个引用值,但业务上仍需手动 mutate 内部字段(比如通过Object.assign或直接赋值)。
❌ 常见误解澄清
-
shallowRef不是readonly(shallowRef(...))的简写 —— 它完全不提供只读保障; -
shallowRef({ x: 1 }).value.x = 2是合法 JS 操作,但不会触发视图更新(因为内部没响应式),也不会被阻止(因为没只读保护); - 如果你只想要“只读”,不用
shallowRef也能实现:readonly(reactive({...}))或readonly(ref({...})),但会带来不必要的深层响应开销。
✅ 总结:按目标选组合
| 你想实现的效果 | 推荐组合 |
|---|---|
| 顶层响应 + 整个对象深度只读(推荐冻结配置) | readonly(shallowRef(...)) |
| 顶层响应 + 仅顶层只读(允许改内部字段) | shallowReadonly(shallowRef(...)) |
| 仅需只读、不关心响应性(静态快照) |
readonly(reactive(...)) 或 readonly({...})
|
不需要额外封装,Vue 的这些 API 天然支持自由组合。关键在理解每层的作用边界:shallowRef 控制响应深度,readonly/shallowReadonly 控制可写性范围。


















