readonly用于将响应式对象包装为只读代理,保持响应式更新但拦截写操作;适用于安全暴露状态给子组件或第三方,需注意其浅层只读特性,深层只读需嵌套调用。

在 Vue 3 的 Composition API 中,readonly 是一个非常实用的工具函数,它能将响应式对象(或 ref)包装为只读版本——外部无法修改其属性,但依然保持响应式更新能力。这不是简单的“禁止赋值”,而是通过 Proxy 拦截所有设置操作并抛出警告(开发环境)或静默失败(生产环境),从而在逻辑层实现状态保护。
什么时候该用 readonly?
当你需要把内部管理的状态安全地暴露给子组件、第三方库或协作模块,又不希望它们意外修改时,readonly 就是首选。典型场景包括:
- 父组件向子组件传递响应式 props(如
defineProps返回的本身就是只读的,但自定义透传时需手动处理) - 封装可复用的组合式函数(composable),返回状态时防止调用方误改
- 从 store 或全局状态中提取一部分作为只读视图(例如仅展示用户信息,不开放编辑)
如何正确使用 readonly?
readonly 接收一个响应式对象(reactive、ref、computed 等)并返回其只读代理。注意:它只作用于最外层,嵌套对象默认仍是可写的(除非内部也显式调用 readonly)。
示例:
import { reactive, readonly, ref } from 'vue'
<p>// ✅ 对 reactive 对象做只读包装
const state = reactive({ count: 0, user: { name: 'Alice' } })
const readOnlyState = readonly(state)</p><p>// ❌ 下面会触发警告(开发环境)或静默失败(生产环境)
readOnlyState.count = 1 // 不生效
readOnlyState.user.name = 'Bob' // 仍可修改!因为 user 本身不是只读</p><p>// ✅ 若需深层只读,可对嵌套对象也包装
const deepReadOnlyState = readonly({
...state,
user: readonly(state.user)
})</p>readonly 和 const、Object.freeze 的区别
const 只限制变量绑定不可重新赋值,不影响对象内部属性修改;Object.freeze 是浅层冻结,且不具备响应式能力——Vue 无法追踪其变化;而 readonly 是响应式系统深度集成的只读机制:
- 保持响应式依赖追踪(模板中仍能自动更新)
- 拦截所有写操作(包括属性新增、删除、赋值)
- 与
shallowReadonly配合可控制只读深度 - 类型层面也会反映为只读类型(TypeScript 中推导为
DeepReadonly)
配合 computed 实现派生只读状态
对于需要计算后暴露的只读值,优先用 computed 而非 readonly。因为 computed 天然只读且缓存,语义更清晰:
const count = ref(0) // ✅ 推荐:计算属性天然只读 + 响应式 + 缓存 const doubled = computed(() => count.value * 2) <p>// ⚠️ 不必要:readonly 包裹 ref 也能只读,但无缓存、无计算逻辑 const doubledAlt = readonly(computed(() => count.value * 2))</p>
简单说:要“只读+响应式+防篡改”,用 readonly;要“只读+响应式+自动计算”,用 computed。










