#Vue3 中 readonly 的应用场景详解
readonly 是 Vue3 提供的核心响应式 API 之一,用于创建深层次只读的响应式代理对象。其核心价值在于 保护数据不可变性,同时保持数据的响应式追踪。以下是其典型应用场景:
- 全局配置保护
场景说明
保护全局配置对象(如应用主题、权限规则等),防止在任意组件中被意外修改。
示例代码
import { reactive, readonly } from 'vue'
// 全局配置对象(原始响应式)
const globalConfig = reactive({
theme: 'dark',
apiEndpoint: 'https://api.example.com'
})
// 对外暴露只读版本
export const readonlyConfig = readonly(globalConfig)
// 仅在特定模块中允许修改原始配置
function updateTheme(newTheme) {
globalConfig.theme = newTheme // 允许修改原始对象
}
效果
- 其他模块引入
readonlyConfig时,修改theme会触发警告(开发模式)且操作无效。 - 原始
globalConfig的修改仍会影响所有依赖readonlyConfig的组件。
- 跨模块数据传递
场景说明
当父模块向子模块传递数据时,若子模块无需修改数据,使用readonly可强制保证数据流的单向性。
示例代码
// 父组件
import { reactive, readonly } from 'vue'
const parentData = reactive({
user: { name: 'Alice' },
permissions: ['read', 'write']
})
// 向子组件传递只读数据
<ChildComponent :data="readonly(parentData)" />
效果
- 子组件直接修改
data.user.name会失败。 - 父组件修改
parentData.user.name时,子组件仍能响应更新。
- 生成不可变快照
场景说明
记录某一时刻的数据状态(如表单提交前的初始值、操作日志的瞬时值),确保快照不被后续操作影响。
示例代码
import { reactive, readonly } from 'vue'
const formState = reactive({
username: '',
address: { city: 'Beijing' }
})
// 提交前保存快照
const submitSnapshot = readonly({ ...formState })
// 后续操作无法修改快照
submitSnapshot.username = 'Bob' // 控制台警告且无效
- 状态管理库集成
场景说明
在 Pinia 或 Vuex 中,对外暴露只读状态,强制通过 Actions 修改数据。
示例代码
// Store 定义
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const state = reactive({ name: 'Alice', role: 'admin' })
const readonlyState = readonly(state)
function updateName(newName) {
state.name = newName // 仅通过 Action 修改
}
return { state: readonlyState, updateName }
})
效果
- 组件中直接修改
state.name会失败,必须调用updateName方法。
- 性能敏感场景
场景说明
对大型嵌套对象(如树形结构)使用readonly,可减少深层响应式代理的开销。
(需权衡:虽然readonly会跳过深层代理,但需确保确实不需要修改深层属性)
示例代码
const bigData = readonly({
nodes: [
{ id: 1, children: [/* 大量嵌套数据 */] }
]
})
注意事项
- 与
shallowReadonly的区别readonly:递归所有层级,完全不可变。shallowReadonly:仅顶层属性不可变,嵌套对象仍可变。
const data = shallowReadonly({
a: 1,
nested: { b: 2 }
})
data.a = 2 // 无效
data.nested.b = 3 // 有效!
- 代理与原始对象的关系
readonly代理的值会随原始对象的修改同步更新。- 直接修改代理对象无效,但原始对象仍可修改。
- 类型安全
在 TypeScript 中,readonly代理会继承原始对象的类型,但所有属性变为只读:
interface User { name: string; age: number }
const user: User = reactive({ name: 'Alice', age: 30 })
const readonlyUser = readonly(user)
// 类型自动推导为 Readonly<User>
总结表格
| 场景 | readonly 适用性 | 替代方案 |
|---|---|---|
| 全局配置保护 | ✅ 完全不可变 | shallowReadonly |
| 跨模块单向数据流 | ✅ 强制只读 | 深拷贝 + reactive |
| 不可变快照 | ✅ 数据隔离 | Object.freeze |
| 状态管理库集成 | ✅ 安全状态暴露 | 封装 Getter 方法 |
| 大型数据只读访问 | ⚠️ 需权衡性能 | shallowReactive |
通过合理使用 readonly,可以在保障数据安全性的同时,维持 Vue3 响应式系统的灵活性。