上周 Code Review,我看到同事写了这样一段代码:
const state = reactive({
user: null,
loading: false,
error: '',
list: []
});
// 后面又单独定义
const currentPage = ref(1);
const pageSize = ref(10);
乍看没问题,但一运行**——页面卡顿、watch 失效、调试器里数据对不上……**
问题出在哪?
不是逻辑错,而是响应式对象的“组合方式”错了。
今天,我就用3 条黄金法则 + 2 个实战模板,帮你彻底搞懂 Vue 3 响应式怎么写才高效、安全、可维护。
法则 1:简单值用 ref,复杂对象用 reactive —— 但别混用!
很多教程说:“primitive 用 ref,object 用 reactive”,这没错,但忽略了“解构陷阱”。
错误示范:
const { user, loading } = reactive({ user: null, loading: false });
// 解构后失去响应性!
正确做法:
// 方案 A:全部用 ref(推荐新手)
const user = ref(null);
const loading = ref(false);
// 方案 B:用 toRefs 保持响应性
const state = reactive({ user: null, loading: false });
const { user, loading } = toRefs(state); // ✅ 响应式保留
经验公式:
- 如果你要频繁解构 or 传递单个属性 → 优先用 ref
- 如果是完整状态模块(如表单、列表配置)→ 用 reactive + toRefs
法则 2:别把 ref 套进 reactive,除非你真的需要
见过这种写法吗?
const state = reactive({
count: ref(0), // ❌ 不要!
name: 'Vue'
});
这会导致:
- 访问时必须写 state.count.value(破坏一致性)
- 模板中虽然自动 unwrap,但逻辑层混乱
- 容易引发“value 嵌套地狱”
正确做法:统一层级
// 要么全 ref
const count = ref(0);
const name = ref('Vue');
// 要么全 reactive(count 直接是 number)
const state = reactive({
count: 0,
name: 'Vue'
});
小技巧:在 setup() 返回时,用 ...toRefs(state) 一键暴露所有属性。
法则 3:大型组件,用“状态模块化”代替巨型 reactive
当组件状态超过 5 个字段,别堆在一个 reactive 里!
反面教材:
const state = reactive({
// 用户信息
userId, userName, userAvatar,
// 分页
page, size, total,
// 搜索条件
keyword, status, dateRange,
// UI 状态
showDrawer, loading, errorMsg...
});
推荐拆分:
// 按功能拆成多个小状态块
const userState = reactive({ id: '', name: '', avatar: '' });
const pagination = reactive({ page: 1, size: 10, total: 0 });
const uiState = reactive({ loading: false, drawerVisible: false });
// 或封装成 composable
const { userState } = useUserStore();
const { pagination, fetchList } = usePagination();
这样不仅逻辑清晰,还天然支持 逻辑复用(比如分页逻辑抽成 usePagination)。
实战模板:两种主流写法对比
模板 A:全 ref 风格(适合中小型组件)
export default {
setup() {
const loading = ref(false);
const list = ref([]);
const keyword = ref('');
const search = async () => {
loading.value = true;
list.value = await api.search(keyword.value);
loading.value = false;
};
return { loading, list, keyword, search };
}
}
优点:直观、无解构风险、TS 类型推导友好
注意:返回时别漏写 .value
模板 B:reactive + toRefs(适合状态密集型组件)
export default {
setup() {
const state = reactive({
loading: false,
list: [] as Item[],
keyword: ''
});
const search = async () => {
state.loading = true;
state.list = await api.search(state.keyword);
state.loading = false;
};
return { ...toRefs(state), search };
}
}
优点:状态聚合、减少变量声明、模板中直接用 list
注意:内部操作用 state.xxx,别解构!
高阶建议:结合 更清爽
如果你用 Vue 3.3+,直接上 :
import { ref } from 'vue'
const loading = ref(false)
const list = ref([])
const keyword = ref('')
const search = async () => {
loading.value = true
list.value = await api.search(keyword.value)
loading.value = false
}
没有 return,没有 setup(),变量自动暴露——这才是 Vue 3 的终极舒适区。
最后说两句
Vue 3 的响应式系统很强大,但自由也意味着责任。
用对了,代码清爽如诗;用错了,bug 隐蔽如鬼。
记住三句话:
- 简单用 ref,复杂用 reactive
- 别混用,别嵌套,别解构裸对象
- 大组件,拆状态,抽 composable
各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!