Vue响应式只读保护核心是代理拦截而非锁死数据,必须在源头(父组件/提供方)用readonly或shallowReadonly提前封装;前者深层只读适合配置项,后者仅顶层只读适合根级稳定但内部可变场景,并需配合TS类型与ESLint构建三层防护。

Vue 响应式系统中处理只读状态,核心不是“锁死数据”,而是**在访问路径上加一层拦截代理**,让误修改行为在开发阶段就能被发现、被阻止。它不改变原始数据的可变性,但确保外部通过该代理操作时无法篡改——同时保持响应式更新能力。
什么时候必须用 readonly?
关键在于“谁提供、谁封装”:
- 父组件向子组件传递 reactive 对象或 ref 时,应在父组件里提前用
readonly(userInfo)或readonly(userRef)包装后再传 - 多个子组件共用同一响应式对象,又需防止某一方意外赋值(如
props.user.name = 'Bob') - 在 Pinia store 中暴露状态时,返回
readonly($state),所有变更强制走 action - 通过
provide/inject共享状态,必须对注入值做readonly,否则子组件拿到原始响应式对象就绕过保护
readonly 和 shallowReadonly 怎么选?
区别在于“只读的深度”:
-
readonly:深层只读。嵌套任意层级(
user.profile.address.city)都禁止修改、新增、删除,适合配置项、用户档案等结构稳定的数据 -
shallowReadonly:仅顶层属性只读。例如
stats: reactive({ count: 0 }),允许子组件调用stats.count++,但禁止替换整个stats属性(props.stats = newStats会报错)
两者都保持响应式——原始数据变了,只读代理读出来的值实时更新,视图照常重渲染。
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
立即学习“前端免费学习笔记(深入)”;
常见误区和加固建议
readonly 是运行时防线,不是银弹:
- ❌ 不要在子组件里对
props再套一次readonly—— props 本身已是只读代理,多包一层无意义,还可能干扰类型推导 - ❌ 不能防住解构后的局部变量赋值(
const { name } = readonlyObj; name = 'x'不报错,但这只是改了常量,不影响源数据) - ✅ 配合 TypeScript 类型声明:
defineProps }>(),编辑器直接标红错误赋值 - ✅ 启用 ESLint 规则
vue/no-mutating-props,在写代码时就拦截props.xxx = yyy - ✅ 复杂派生逻辑优先用
computed,比如computed(() => ({ ...state })),再套readonly,比裸用readonly(ref())更自然、更安全
怎么验证是否生效?
打开浏览器控制台,直接操作只读代理:
- 对
readonly(ref(5))执行myRef.value = 10→ 开发环境提示 "Set operation on key 'value' failed: target is readonly" - 对
readonly(reactive({ a: { b: 1 } }))执行obj.a.b = 2或obj.a = {}→ 同样报错 - 如果没警告,检查是否传的是原始对象、是否在生产环境(此时静默失败)、或是否用了
shallowReadonly却误改了嵌套属性

















