必须在 provide 时加 readonly,因为 ref、reactive、computed 下发后子组件解构或赋值会直接修改父状态,inject 无法反向加固;只读性须源头声明,且开发环境报错、生产环境静默拒绝。

Provide 注入的数据本身不带保护机制,子组件拿到的是响应式对象的原始引用,直接修改会破坏状态一致性。防范意外修改的关键,是在 provide 侧 就用 readonly 包裹数据,而不是等 inject 后再处理。
为什么必须在 provide 时加 readonly?
因为:
- ref、reactive、computed 下发后,子组件解构或赋值操作(如
count.value++或obj.name = 'new')会直接改父组件状态 - inject 侧无法“反向加固”已下发的可写引用;只读性必须从源头声明
- 开发环境下,尝试修改 readonly 对象属性会抛出明确警告,生产环境则静默拒绝,语义清晰且有反馈
正确写法:provide 时显式包裹
父组件中:
import { provide, readonly, ref, reactive, computed } from 'vue'
setup() {
const count = ref(0)
const user = reactive({ id: 1, name: 'Tom' })
const theme = computed(() => 'light')
provide('shared', {
count: readonly(count),
user: readonly(user),
theme: readonly(theme)
})
return () => <child />
}
子组件安全使用方式
子组件中应避免提前解构,推荐保持对象访问链:
- ✅ 安全读取:
state.count.value、state.user.name - ❌ 禁止解构后修改:
const { count } = state; count.value++(丢失只读约束) - ❌ 不要重新赋值属性:
state.user.name = 'Jack'(开发环境报错)
注意 readonly 是浅层只读
它只冻结顶层属性的赋值行为,不递归冻结嵌套对象。例如:
-
readonly(reactive({ profile: { age: 25 } }))→state.profile不能被替换,但state.profile.age = 26仍可执行 - 如需深层防护,可组合
shallowRef+readonly,或对嵌套结构单独包裹

















