Vue3 Composition API 状态管理避坑实录
记录 Vue3 从 Options API 迁移到 Composition API 过程中遇到的响应式丢失、ref 自动解包陷阱、watch 监听失效等实际问题,通过真实组件重构案例逐一剖析原因与解决方案,附带 Pinia 最佳实践建议。
# Vue3 Composition API 状态管理避坑实录
## 背景
Vue3 发布至今,Composition API 已成为官方推荐的编码范式。相比 Options API,它在逻辑复用和类型推导上有明显优势。但在实际项目迁移中,不少开发者踩过响应式相关的坑。本文整理了几个高频问题。
## 坑1:reactive 解构后丢失响应式
最经典的陷阱。用 reactive() 包裹对象后,一旦解构赋值,响应式就断了。
```javascript
import { reactive } from 'vue';
const state = reactive({ count: 0, name: 'Vue' });
// 错误:解构后 count 和 name 变成普通值
const { count, name } = state;
count++; // 不会触发更新!
// 正确:用 toRefs 转为 ref 引用
import { toRefs } from 'vue';
const { count, name } = toRefs(state);
count.value++; // ok,模板中可以省略 .value
```
原因:reactive() 返回的是 Proxy 对象,解构相当于取出原始值的副本,脱离了 Proxy 的拦截。
## 坑2:ref 在 reactive 中的自动解包
```javascript
const count = ref(0);
const state = reactive({ count });
state.count++; // 自动解包,不需要 .value
console.log(state.count); // 1
// 但是!
const arr = reactive([ref(0)]);
console.log(arr[0]); // 不解包!返回 RefImpl 对象
arr[0]++; // 不会更新视图
// 数组、Map、Set 等集合类型中 ref 不会自动解包
// 必须 .value:arr[0].value++
```
规律:ref 在 reactive 的普通对象属性中自动解包,但在数组、Map、Set 等集合类型中不解包。
## 坑3:watch 监听 reactive 对象的属性值
```javascript
const state = reactive({ count: 0 });
// 错误:直接监听属性值,拿到的是快照
watch(state.count, (val) => console.log(val));
// 正确:使用 getter 函数
watch(() => state.count, (val) => console.log(val));
// 或者用 watchEffect(自动追踪依赖)
watchEffect(() => console.log(state.count));
```
## 坑4:computed 中的异步操作
```javascript
// computed 不支持异步!
const data = computed(async () => {
const res = await fetch('/api/data');
return res.json(); // 返回的是 Promise,不是数据
});
// data.value 是一个 Promise,模板中无法直接渲染
// 用 ref + watchEffect
const data = ref(null);
watchEffect(async () => {
data.value = await fetch('/api/data').then(r => r.json());
});
```
## 推荐方案:Pinia Setup Store
```javascript
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useUserStore = defineStore('user', () => {
const token = ref('');
const profile = ref(null);
const isLoggedIn = computed(() => !!token.value);
async function login(creds) {
const res = await apiLogin(creds);
token.value = res.token;
profile.value = res.profile;
}
function logout() {
token.value = '';
profile.value = null;
}
return { token, profile, isLoggedIn, login, logout };
});
```
Setup Store 的写法让 Composition API 的经验可以直接复用到状态管理中。
## 总结
| 问题 | 原因 | 解决 |
|------|------|------|
| reactive 解构失效 | 脱离 Proxy | 用 toRefs |
| 数组中 ref 不解包 | 集合类型特殊性 | 手动 .value |
| watch 监听属性值 | getter 才能追踪 | 用 () => x.attr |
| computed 异步 | 设计不支持 | ref + watchEffect |
掌握这些要点,Vue3 响应式体系就能得心应手了。