Vue响应式数据单向流靠父组件用readonly主动封装只读视图,而非禁止传入;子组件props已是浅只读代理,深层修改无警告,需源头包装;readonly与shallowReadonly按结构选型,配合TS类型和ESLint实现三层防护。

Vue 响应式数据的单向数据流强制保护,核心不是靠“禁止传入”,而是靠在源头主动封装只读视图。readonly 不是锁住原始数据,而是给访问路径加一层拦截——子组件拿到的是代理,改它会报错;原始数据照常可变,响应式更新也完全不受影响。
必须在父组件提供时就包装,不能等传到子组件再处理
子组件接收的 props 本身已是只读代理(Vue 内部自动处理),但这个只读仅对顶层属性生效,且对嵌套对象的深层修改无警告。若父组件直接传 reactive 对象过去,子组件写 props.user.profile.level = 'PRO' 不会报错,却已污染状态。
- ✅ 正确:父组件用
readonly(userInfo)或readonly(userRef)包装后再传 - ❌ 错误:子组件里对
props.user再调一次readonly()—— 多余、无效,还可能掩盖类型推导问题 - ⚠️ 注意:
readonly(ref(1))只禁.value = xxx,不防ref(1).value + 1这类读取运算,这本身安全,但别误以为“数字也被锁死了”
深只读 vs 浅只读:按数据结构选对 API
是否需要阻止所有层级的修改,取决于你暴露的数据形态和协作边界。
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
-
readonly():适合完整冻结结构,比如用户档案、配置项、API 返回的嵌套响应式对象。改
user.address.city或新增user.tags全部拦截 -
shallowReadonly():适合根级需稳定、但内部值允许动态更新的场景。例如传一个含
reactive({ count: 0 })的对象,你只要防止子组件替换整个stats属性,但允许它调用stats.count++ - 两者都保持响应式:原始数据变了,只读代理读出来的值实时更新,视图照样重渲染
配合 defineProps 类型 + ESLint,构建三层防护
readonly 是运行时防线,但开发体验要靠静态检查补足。
立即学习“前端免费学习笔记(深入)”;
- 用
defineProps<{ user: Readonly<User> }>()在 TypeScript 中声明只读类型,编辑器能提前标红错误赋值 - 启用
vue/no-mutating-propsESLint 规则,对props.xxx = yyy直接报错,不依赖控制台警告 - 避免通过
provide/inject暴露未包装的响应式对象;如必须共享,provide('user', readonly(user))
调试时快速验证是否生效
打开浏览器控制台,直接操作只读代理:
- 对
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 = {}也失败 - 如果不报错,先确认是否在生产环境(此时静默),再检查是否传的是原始对象而非 readonly 返回值

















