readonly是Vue 3中轻量级响应式只读代理,先reactive再readonly为标准安全组合,配合props/provide、computed/toRefs协同防护,但不作用于普通对象或完全锁定基本类型操作。

在 Vue 3 中,readonly 是保护核心状态不被意外修改最直接、轻量且响应式友好的方式——它不冻结原始数据,也不中断响应更新,而是在访问路径上加一层“只读代理”,开发时赋值即报警,生产时静默拦截。
先 reactive 再 readonly:标准安全组合
这是保护可变状态的首选模式。先用 reactive 构建响应式对象,再用 readonly 封装,得到一个深度只读但实时同步的视图:
- 原对象仍可自由修改(如父组件内部逻辑更新),只读副本自动响应变化
- 嵌套属性(如
user.profile.avatar、list[0].id)默认受保护,无需额外配置 - 子组件或注入方拿到的是代理对象,任何写操作(赋值、删除、新增属性)都会触发警告
配合 props 和 provide:从源头阻断误改
保护必须前置,不能等数据传到子组件才处理:
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
- 父组件传
props前就包装:<Child :config="readonly(appConfig)" /> - 祖先组件
provide时必须用readonly:provide('theme', readonly(themeState)) - 避免在子组件中对
props或inject结果再次readonly——它已是只读代理,重复包装无意义,还可能掩盖原始问题
与 computed 和 toRefs 协同加固
单独 computed 默认只读,但仅限于计算 ref 本身;若需确保底层数据也不被间接修改,应显式包裹:
立即学习“前端免费学习笔记(深入)”;
- 组合式函数内统一封装:
displayName: readonly(computed(() => user.name)) - 解构时保持只读性:必须
toRefs(readonly(state)),不能readonly(toRefs(state)),否则解构出的每个 ref 都失去只读约束 - 对只读 ref 解构后,
const { value } = safeRef中的value仍是只读 proxy 属性,无法赋值
注意边界:readonly 不是万能锁
它防护的是 Vue 响应式代理路径,不是整个 JS 运行时:
- 不作用于普通对象(如 API 直接返回的 plain object),需先转为响应式再 readonly
- 对
Map、Set、Date等非 plain object 类型,深度保护有限,必要时搭配shallowReadonly+ 手动包装 - 无法阻止对基本类型 ref 的运算(如
readonly(ref(5)).value + 1),这本身不危险,但不代表“数据完全锁定” - 应配合
defineProps类型声明、ESLint 规则(如vue/no-mutating-props)形成多层防护

















