Vue3 中 `readonly` 的应用场景详解

203 阅读3分钟

#Vue3 中 readonly 的应用场景详解

readonly 是 Vue3 提供的核心响应式 API 之一,用于创建深层次只读的响应式代理对象。其核心价值在于 保护数据不可变性,同时保持数据的响应式追踪。以下是其典型应用场景:


  1. 全局配置保护 场景说明
    保护全局配置对象(如应用主题、权限规则等),防止在任意组件中被意外修改。

示例代码

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 的组件。

  1. 跨模块数据传递 场景说明
    当父模块向子模块传递数据时,若子模块无需修改数据,使用 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 时,子组件仍能响应更新。

  1. 生成不可变快照 场景说明
    记录某一时刻的数据状态(如表单提交前的初始值、操作日志的瞬时值),确保快照不被后续操作影响。

示例代码

import { reactive, readonly } from 'vue'
 
const formState = reactive({ 
  username: '', 
  address: { city: 'Beijing' } 
})
 
// 提交前保存快照 
const submitSnapshot = readonly({ ...formState })
 
// 后续操作无法修改快照 
submitSnapshot.username = 'Bob' // 控制台警告且无效 
 
 

  1. 状态管理库集成 场景说明
    在 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 方法。

  1. 性能敏感场景 场景说明
    对大型嵌套对象(如树形结构)使用 readonly,可减少深层响应式代理的开销。
    (需权衡:虽然 readonly 会跳过深层代理,但需确保确实不需要修改深层属性)

示例代码

const bigData = readonly({
  nodes: [
    { id: 1, children: [/* 大量嵌套数据 */] }
  ]
})
 
 

注意事项

  1. shallowReadonly 的区别
    • readonly:递归所有层级,完全不可变。
    • shallowReadonly:仅顶层属性不可变,嵌套对象仍可变。
   const data = shallowReadonly({ 
     a: 1, 
     nested: { b: 2 } 
   })
   data.a = 2 // 无效 
   data.nested.b = 3 // 有效!
  1. 代理与原始对象的关系
    • readonly 代理的值会随原始对象的修改同步更新。
    • 直接修改代理对象无效,但原始对象仍可修改。
  2. 类型安全
    在 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 响应式系统的灵活性。