readonly 提供深层只读语义保障,shallowReadonly 仅保护顶层属性;二者均通过 Proxy 拦截赋值删除操作,开发环境警告、生产环境静默失败,不冻结原始数据但防止意外修改。

Vue 中的 readonly 和 shallowReadonly 并不真正“冻结”数据,而是通过 Proxy 拦截赋值、删除等操作,在开发环境发出警告、生产环境静默失败,从而在运行时提供**语义级不可变保障**——它们不阻止原始数据变更,但能有效防止意外修改,配合合理设计可达成接近不可变的效果。
readonly:深层只读,适合强约束场景
它递归代理整个响应式对象(包括嵌套对象、数组及其元素),对任意层级的属性赋值(如 obj.nested.count = 5)、push、delete 等操作均被拦截:
- 开发模式下抛出明确警告,提示“Cannot assign to read only property”
- 生产模式下静默忽略,不报错也不生效,视图仍能响应原始数据变化
- 返回的代理仍是响应式的——原始数据更新,readonly 代理读取值同步更新
- TypeScript 类型中所有属性自动标记为
readonly,提升类型安全
shallowReadonly:仅保护顶层,保留深层可变性
它只拦截对象自身第一层属性的操作,嵌套结构(哪怕已是 reactive)保持原样,不递归包装:
使用 `@ainative/vue-sdk` 为 Vue 3 应用添加 AI 聊天功能。适用于以下场景:(1) 安装 `@ainative/vue-sdk`,(2) 在 Vue 组件中使用 `useChat` 组合式函数,(3) 提供……
-
obj.name = 'new'会警告并失败 -
obj.config.timeout = 10000成功(只要config是普通或 reactive 对象) - 适用于“防替换、不禁修改”的场景,比如组件接收配置对象但允许内部状态动态更新
- 比
readonly开销更低,尤其面对大型嵌套结构时
关键注意事项与加固手段
仅靠这两个 API 不足以构建绝对不可变系统,需结合其他策略:
立即学习“前端免费学习笔记(深入)”;
- 解构后赋值无法拦截:
const { name } = readonlyObj; name = 'x'只改局部变量,不触发警告 - 嵌套普通对象不受保护:若
obj.nested是普通对象,shallowReadonly不处理它,readonly才会递归代理 - 建议搭配
computed封装派生状态:用computed(() => ({ ... }))+readonly()避免暴露原始引用 - 对关键状态,应在业务逻辑层主动校验:用
watch监听原始 ref/reactive,发现异常修改时记录或中断
何时选哪个?一句话判断
想让整个数据树都“碰不得”,选 readonly;只想锁住顶层字段(比如防止 props.config = newConfig),同时允许 config.loading = true 这类内部更新,就用 shallowReadonly。

















