Vue3 中 reactive 与 readonly 的深度解析
一、核心功能对比
| 特性 | reactive | readonly | shallowReadonly |
|---|---|---|---|
| 响应式类型 | 深层响应式 | 深层只读 | 浅层只读(仅第一层属性不可变) |
| 数据修改 | 允许修改所有层级属性 | 禁止修改,触发警告(开发环境) | 禁止修改第一层属性,深层可变 |
| 适用场景 | 可变状态管理(如组件内部数据) | 保护全局配置、传递不可变数据 | 保护对象顶层结构 |
| 嵌套对象处理 | 自动递归代理 | 递归代理为只读 | 仅代理第一层,深层保留原始状态 |
二、使用场景与示例
reactive基础用法
import { reactive } from 'vue';
// 创建深层响应式对象
const state = reactive({
count: 0,
user: { name: 'Alice' }
});
// 修改属性
state.count = 1; // 触发响应式更新
state.user.name = 'Bob'; // 深层属性修改同样触发更新
readonly保护数据
import { reactive, readonly } from 'vue';
const source = reactive({ config: { theme: 'dark' } });
// 创建只读代理
const readOnlyConfig = readonly(source);
// 尝试修改会触发警告(开发环境)
readOnlyConfig.config.theme = 'light'; // 控制台警告:Set operation on key "theme" failed
shallowReadonly限制表层修改
import { shallowReadonly } from 'vue';
const state = shallowReadonly({
settings: { fontSize: 16 }
});
state.settings = {}; // 禁止修改(第一层属性)
state.settings.fontSize = 14; // 允许修改(深层属性)
三、源码级特性说明
- 代理机制
reactive使用Proxy递归代理对象的所有层级。readonly的Proxy会拦截set/deleteProperty操作并阻止修改 [6]。
- 性能优化
- 深层只读 (
readonly) 比浅层 (shallowReadonly) 有更高的初始化开销,适合稳定数据。 - 浅层只读适用于动态层级结构(如配置对象)[4]。
- 深层只读 (
- 类型兼容性
// 类型推断示例 interface State { id: number; metadata: { tags: string[] }; } const state: Readonly<State> = readonly(reactive<State>({ id: 1, metadata: { tags: ['vue'] } }));
---
四、常见问题与解决
1. 误修改只读数据导致警告
现象:控制台提示 `Set operation on key "xxx" failed`。
解决:
- 检查数据来源是否为 `readonly` 包装对象。
- 使用 `isReadonly()` 进行运行时验证:
```javascript
import { isReadonly } from 'vue';
if (!isReadonly(data)) {
// 安全修改逻辑
}
- 深层对象意外变更
场景:
shallowReadonly的深层属性被意外修改。
方案:
- 改用
readonly或手动冻结深层对象:const state = shallowReadonly({ nested: Object.freeze({ value: 42 }) });
---
五、最佳实践
1. 状态分层设计
- 使用 `reactive` 管理组件内部可变状态。
- 通过 `readonly` 向子组件传递数据,避免副作用 [[2]](https://blog.csdn.net/weixin_47450807/article/details/122889417)。
2. 性能敏感场景
- 对高频更新的数据避免深层只读,优先使用 `shallowReactive` 或 `shallowReadonly`[[5]](https://www.jb51.net/javascript/320439nu3.htm)。
3. TypeScript 增强
- 结合 `Readonly<T>` 类型强化只读约束:
```typescript
import { reactive, readonly } from 'vue';
type Config = Readonly<{ apiUrl: string }>;
const config: Config = readonly(reactive({ apiUrl: 'https://api.example.com' }));
```
通过合理运用 `reactive` 和 `readonly`,可以在保证数据安全性的同时,充分利用 Vue3 响应式系统的能力。