readonly 为响应式对象创建深层只读代理,拦截所有修改操作;必须用于父传子状态、provide/inject 配置、pinia 展示数据及只读函数参数,且应在源头用 readonly(ref) 或 readonly(obj) 封装。

readonly 不是给变量“上锁”,而是为响应式对象创建一个只读代理层。它不阻止原始数据被改,但能拦截所有通过该代理发起的修改操作——开发时报警告,生产环境静默失败。
什么时候必须用 readonly
关键不是“能不能用”,而是“该不该提前包”。典型必须用的场景:
- 父组件把 ref 或 reactive 创建的状态传给子组件,又不希望子组件意外改写
- 通过 provide/inject 向深层组件暴露配置或共享状态,需防止下游任意篡改
- 从 Pinia store 中取出 state 片段,仅用于展示(比如用户信息、系统常量),不参与业务更新逻辑
- 函数参数接收响应式对象时,明确声明“我只读不负责改”,提升接口语义清晰度
怎么正确包装:时机和对象都得对
错误做法:在子组件里对 props 再套一层 readonly —— props 本身已是只读代理,多包一层没意义,还可能掩盖原始问题。
分析 Vue 3 Composition API 使用情况——审计响应式模式、可组合函数设计、ref 与 reactive 的选择、计算属性、watcher 使用及组件……
正确做法:在数据源头就封装好:
- 对 ref:用
readonly(myRef),返回只读 ref,.value赋值会被拦截 - 对 reactive 对象:用
readonly(myObj),返回只读代理,所有属性(含嵌套)均不可增、删、改 - 嵌套结构自动受保护:比如
readonly({ user: { name: 'A' } }),连user.name = 'B'都会报错
readonly 和 shallowReadonly 的区别
两者都返回只读代理,但“只读”的深度不同:
- readonly 是深层只读:整个对象树都被冻结,包括所有嵌套对象、数组项、对象属性值中的响应式对象
-
shallowReadonly 是浅层只读:只锁定第一层属性,内部嵌套对象仍可自由修改(例如
shallowReadonly({ a: { b: 1 } }).a.b = 2允许) - 保护全局状态、配置对象、展示型数据,优先选
readonly;只有当你明确需要“禁止替换整个对象,但允许更新子字段”时,才考虑shallowReadonly
它不能替代设计约束
readonly 是防御性工具,不是万能锁:
- 如果子组件绕过 props,直接拿到原始响应式对象(比如通过未包装的 provide/inject),readonly 就失效
- 对基本类型 ref(如
ref(42)),readonly(ref(42))只拦.value = xxx,不拦数学运算(这本身安全,但别误以为“完全锁死”) - 非响应式对象(如 API 返回的 plain object)经 readonly 包装后,仍是普通对象,无拦截能力
- 建议搭配
defineProps类型声明 + ESLint 规则(如vue/no-mutating-props)形成多层防护
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!









