Vue 的 readonly 返回只读代理而非不可变对象,阻止写操作但不保护原始响应式对象;它深度只读、保持响应性,区别于 const 和 Object.freeze,适用于安全暴露状态。

Vue.js 中的 readonly 并不创建“不可变对象”,而是返回一个**只读代理(read-only proxy)**,它阻止对响应式数据的写操作(包括属性赋值、删除、添加),但原始对象本身仍可被修改——这点容易被误解。
readonly 的作用范围是代理层,不是原始数据
readonly() 包裹的是响应式对象(如 reactive() 或 ref() 的解包值),返回一个禁止写入的代理。但如果你保留了原始响应式对象的引用,依然可以直接修改它:
- 原始对象未受保护:若你同时持有
const state = reactive({ count: 1 })和const roState = readonly(state),修改state.count = 2会生效,且roState.count也会变成 2(因为它们共享同一底层数据) - 只读代理报错:尝试
roState.count = 3会在开发模式下触发警告,并静默失败(生产模式无提示) - 深层只读:
readonly()是深度的,嵌套对象/数组也受保护,无法修改任何层级的属性
与 const、Object.freeze 的区别
readonly 是 Vue 响应式系统的一部分,和 JavaScript 原生机制不同:
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
-
const obj = { a: 1 }只防止变量重新赋值,不阻止属性修改 -
Object.freeze(obj)确实冻结对象(非严格模式下静默失败),但它破坏响应性:Vue 无法再追踪其变化,也不能在模板中响应更新 -
readonly(reactive(...))既保持响应性,又提供运行时写入防护,适合用于 props 或跨组件只读共享状态
常见使用场景和注意事项
典型用途是安全暴露响应式数据给子组件或外部逻辑,避免意外修改:
立即学习“前端免费学习笔记(深入)”;
- 在组合式 API 中,父组件向子组件传递只读状态:
setup() { const state = reactive({ x: 0 }); return { state: readonly(state) }; } - 配合
toRefs()使用时注意:应先readonly再toRefs,否则 ref 解包后可能绕过只读限制 - 不能用于普通对象直接包裹:若传入非响应式对象(如 plain object),
readonly仍返回只读代理,但失去响应性——它不是响应式转换工具 - 类型层面:TypeScript 中
readonly会推导出只读类型(Readonly<T>),与运行时行为协同,提升类型安全
替代方案:shallowReadonly 与自定义逻辑
如果只需要顶层属性只读,嵌套对象仍需可写,用 shallowReadonly():
- 它只代理第一层,内部嵌套的响应式对象(如
state.nested = reactive({ y: 1 }))仍可修改 - 真正需要不可变语义(如函数式编程风格),应结合
immer或immutable-js在业务逻辑层处理,Vue 的readonly不解决数据持久化问题 - 若需彻底隔离,建议将数据封装为 getter 函数或使用 computed 返回派生只读状态

















