readonly() 包裹 reactive()/ref() 可创建响应式只读副本,开发环境修改报警告、生产环境静默失败;支持深度只读、配合 toRefs 需先 readonly 再解构,与 computed 结合可强化不可变语义。

直接用 readonly() 包裹 reactive() 或 ref() 创建的状态,就能得到一个响应式但不可变的只读副本——它允许读取、自动更新,但所有修改操作(赋值、增删属性)在开发环境会报警告,生产环境静默失败。
基础组合:reactive + readonly
这是最常用的方式,适合保护整个对象状态:
- 先用
reactive创建可变响应式源,再用readonly封装,返回只读代理 - 原对象仍可自由修改,只读副本会同步响应变化(共享同一响应式系统)
- 嵌套结构默认深度只读:修改
safeState.user.role或safeState.items[0].id都会被拦截
传给子组件前加 readonly,防误改
父组件向子组件传递状态时,推荐在 provide 或 v-bind 前就包装:
- 避免子组件通过
props.user.name = 'xxx'意外篡改数据 - 比靠文档约定更可靠,是防御性编程的实际落地
- 示例:
<UserProfile :user="readonly(userData)" />
配合 toRefs 解构时注意顺序
如果需要解构使用,必须先 readonly,再 toRefs,否则只读性丢失:
立即学习“前端免费学习笔记(深入)”;
- ✅ 正确:
const { count } = toRefs(readonly(state))→count是只读 ref - ❌ 错误:
const { count } = toRefs(state)再readonly(count)→ 失去对解构后 ref 的写保护
与 computed 结合强化语义
计算属性本身默认只读,但仅阻止对 .value 赋值,不锁底层源。如需绝对安全,可再包一层:
- 例如:
const safeTitle = readonly(computed(() => user.name.toUpperCase())) - 这样即使子组件解构出
title.value,也无法通过赋值改变它 - 在组合式函数中统一封装(如
useUserInfo),调用方无需二次处理


















