Vue3 readonly与shallowReadonly保护状态不被修改
Vue3提供了多种状态管理方式,其中readonly和shallowReadonly是保护状态不被修改的重要工具。理解这两个API的工作原理和适用场景,可以帮助开发者更好地控制应用状态,避免意外的状态变更。
1. 认识readonly
readonly是Vue3提供的响应式API,用于将响应式对象或数组转换为只读版本。应用readonly后,任何对返回对象属性的修改操作都会在开发环境下发出警告。
创建一个只读对象非常简单:
import { reactive, readonly } from 'vue'
const original = reactive({
count: 0,
nested: {
value: 10
}
})
const readOnlyOriginal = readonly(original)
使用readonly后,以下操作会触发警告:
readOnlyOriginal.count++ // 警告:Set operation on key "count" failed: target is readonly.
readOnlyOriginal.nested.value = 20 // 警告:Set operation on key "value" failed: target is readonly.
2. readonly的特性
了解readonly的核心特性有助于更好地使用它:
- 深度只读:readonly会创建一个完全不可变的代理,包括嵌套对象和数组
- 保持响应式:只读对象仍然是响应式的,可以跟踪依赖变化
- 警告提示:在开发环境下尝试修改会发出警告,生产环境下静默失败
- 引用保持:返回的对象与原始对象保持相同的引用,可以追踪变化
检查对象是否为只读:
import { isReadonly } from 'vue'
console.log(isReadonly(readOnlyOriginal)) // true
3. 认识shallowReadonly
shallowReadonly是readonly的"浅层"版本。应用shallowReadonly后,只有顶层属性是只读的,嵌套对象的属性仍然可以被修改。
创建一个浅层只读对象:
import { reactive, shallowReadonly } from 'vue'
const original = reactive({
count: 0,
nested: {
value: 10
}
})
const shallowReadOnlyOriginal = shallowReadonly(original)
使用shallowReadonly后,以下行为会不同:
shallowReadOnlyOriginal.count++ // 警告:Set operation on key "count" failed: target is readonly.
shallowReadOnlyOriginal.nested.value = 20 // 不会警告,修改成功
4. readonly与shallowReadonly对比
了解两者的差异有助于在不同场景下做出正确选择:
| 特性 | readonly | shallowReadonly |
|---|---|---|
| 深度 | 深度只读 | 仅顶层只读 |
| 嵌套修改 | 禁止 | 允许 |
| 性能 | 较高开销(遍历所有嵌套属性) | 较低开销(仅代理顶层属性) |
| 适用场景 | 需要完全保护的数据结构 | 需要保护顶层但允许修改嵌套属性的场景 |
5. 实际应用场景
5.1 使用readonly
场景:保护从API获取的不应该被修改的配置数据。
// 在store或组件中
import { readonly } from 'vue'
export const useConfigStore = defineStore('config', () => {
const config = reactive({
apiEndpoints: {
user: '/api/users',
product: '/api/products'
},
settings: {
timeout: 5000,
retries: 3
}
})
// 暴露为只读,防止意外修改
return readonly(config)
})
5.2 使用shallowReadonly
场景:保护组件props的顶层属性,但允许修改深层状态。
// 父组件
const userProfile = reactive({
userInfo: {
name: 'John',
age: 30
},
preferences: {
theme: 'dark',
notifications: true
}
})
const userProfileReadOnly = shallowReadonly(userProfile)
// 传递给子组件
<ChildComponent :user-profile="userProfileReadOnly" />
// 子组件中
props: {
userProfile: Object
}
// 子组件内尝试修改
props.userProfile.userInfo.name = 'Jane' // 成功
props.userProfile.preferences.theme = 'light' // 成功
props.userProfile = {} // 警告:失败,顶层属性是只读的
6. 在组合式API中的应用
保护组合式API中的状态是一个常见需求:
import { ref, readonly, shallowReadonly } from 'vue'
export function useUser() {
const user = ref({
name: 'John',
age: 30,
preferences: {
theme: 'dark',
notifications: true
}
})
// 如果需要完全保护
const readonlyUser = readonly(user)
// 如果需要保护顶层但允许修改preferences
const shallowReadOnlyUser = shallowReadonly(user)
return { user: shallowReadOnlyUser, updateUser }
}
7. 在Pinia状态管理中的应用
结合Pinia使用readonly可以有效保护状态不被意外修改:
import { defineStore } from 'pinia'
import { readonly, shallowReadonly } from 'vue'
export const useUserStore = defineStore('user', () => {
const user = reactive({
id: 1,
name: 'John',
preferences: {
theme: 'dark',
notifications: true
}
})
const updateUser = (newUserData) => {
Object.assign(user, newUserData)
}
// 返回浅层只读状态
return {
// 如果完全保护,使用 readonly(user)
// 如果允许修改嵌套属性,使用 shallowReadonly(user)
user: shallowReadonly(user),
updateUser
}
})
8. 最佳实践
遵循以下原则可以有效使用readonly和shallowReadonly:
-
选择合适的保护级别:
- 如果数据需要完全不可变,使用
readonly - 如果需要保护顶层结构但允许修改嵌套数据,使用
shallowReadonly
- 如果数据需要完全不可变,使用
-
在组件中保护props:
import { shallowReadonly } from 'vue' export default { setup(props) { // 保护props不被修改 return { readonlyProps: shallowReadonly(props) } } } -
在状态库中保护状态:
// 在store/index.js import { reactive, readonly } from 'vue' export const state = reactive({ // state data }) export const readonlyState = readonly(state) -
性能考虑:对于深层嵌套对象,优先使用
shallowReadonly以减少性能开销
记住,readonly和shallowReadonly在开发模式下会提供警告,帮助识别意外修改,但在生产模式下这些警告会被禁用,修改操作会静默失败。

暂无评论,快来抢沙发吧!