文章目录
前言
在我的开发历程中,遇到过两个看似矛盾的问题:
第一个场景: 在不同页面使用相同参数调用不同接口获取不同列表数据。我按照项目已有模式将所有请求和参数都放在Store中,结果发现数据会被"污染"——不同页面的数据会相互影响。
第二个场景:需要复用数据时,我将数据写在Hooks中,期望多次请求能累积数据,却发现每次都是重新赋值,无法保持状态。
这两个相反的现象让我产生疑惑,开始了解Vue3中Hooks和Store的本质区别才恍然大悟。
以下是详细对比:
一、Hooks与Store对比
1. 设计目的
- Hooks:本质是可复用的逻辑组合,每次调用都会创建独立的状态和副作用
- Store:设计为集中式状态管理,整个应用共享同一状态
2. 生命周期
- Hooks:状态与组件实例绑定,组件卸载即销毁
- Store:状态与应用生命周期一致,持久存在
3. 状态共享
- Hooks:默认不共享状态,每次调用都是独立实例
- Store:全局单例,所有组件访问同一状态
区别总结
特性 | Pinia 的 state | 钩子(hooks)中的变量 |
---|---|---|
数据存储位置 | 全局单例(整个应用共享同一份数据) | 每次调用钩子时创建新实例(独立数据) |
数据共享性 | ✅ 所有组件/钩子访问同一数据 | ❌ 每次调用创建独立实例 |
响应式传递 | 自动跨组件响应式更新 | 需手动传递引用或提升作用域 |
生命周期 | 应用生命周期 | 随组件创建/销毁 |
数据流向 | 集中存储→各组件读取/修改 | 输入→处理→输出 |
典型用途 | 单例模式、全局状态(如用户信息、主题) | 工厂函数模式、 局部逻辑复用(如表单验证) |
二、场景案例
作用对比
1. 钩子(Hooks)中的变量(独立数据)
// useList.js
export default function() {
// 每次调用 useList() 都会创建新的 list
const list = ref([]);
const getInfo = async () => {
const res = await fetch('/api/data');
list.value = res.data; // 仅修改当前实例的 list
};
return { list, getInfo };
}
// 组件A
const { list: listA, getInfo } = useList();
getInfo(); // listA 被赋值
// 组件B
const { list: listB } = useList();
console.log(listB.value); // [] ❌ 空数组(与 listA 无关)
2. Pinia 的 state
(共享数据)
// stores/lotteryStore.js
import { defineStore } from 'pinia';
export const useListStore = defineStore('list', {
state: () => ({ list: [] }),
actions: {
async getInfo() {
const res = await fetch('/api/data');
this.list = res.data; // 修改全局共享的 list
}
}
});
// 组件A
const storeA = useListStore();
storeA.getInfo(); // 修改全局 list
// 组件B
const storeB = useListStore();
console.log(storeB.list); // ✅ 拿到最新数据(与 storeA 共享)
得到结论
-
钩子hooks中的变量
- 每次调用
useXxx()
会创建 全新的变量实例,数据隔离。 - 适合封装独立逻辑(如一个组件的动画控制),不适用于跨组件共享状态。
- 单一职责,避免过于复杂
- 合理使用
computed
和watch
增强功能
- 每次调用
-
Pinia 的
state
- 整个应用维护同一份数据,任何地方调用都会读写同一状态。
- 适合管理全局共享数据(如用户登录信息、购物车)。
- -每个功能模块有自己独立的Store,只关注一个特定业务领域
- 避免过度集中化,合理划分边界
(用户useUserStore和商品管理useProductStore要放在两个不同的store中)
- 考虑使用
pinia
的storeToRefs
保持响应式
无论是Hooks还是Store,都应该遵循"一个模块只做一件事"的原则. 都建议按功能/业务逻辑进行拆分
如何选择?
场景 | 推荐方案 | 原因 |
---|---|---|
组件内部复杂逻辑抽离 | Hooks | 保持逻辑内聚,不污染全局 |
跨组件共享状态 | Store | 避免props drilling |
短暂UI状态(如表单) | Hooks | 组件卸载即销毁 |
持久化应用数据 | Store | 需要长期维护 |
相同逻辑不同状态 | Hooks | 每次调用独立实例 |
全局唯一数据源 | Store | 保证数据一致性 |
除了各自使用功能之外,也可以混合使用
// 示例:将Store注入Hooks实现灵活复用
export function useUser() {
const userStore = useUserStore()
const isAdmin = computed(() => userStore.role === 'admin')
return {
user: storeToRefs(userStore),
isAdmin
}
}
钩子间共享数据方式
如果希望钩子间共享数据,但不想用 Pinia,可用 单例模式 + ref
:
// useSharedData.js
const sharedData = ref(null); // 模块作用域的 ref
export const useSharedData = () => {
const updateData = (newData) => {
sharedData.value = newData; // 修改共享数据
};
return { data: sharedData, updateData }; // 返回同一 ref
};
// 所有调用 useSharedData() 的地方共享 data
总结
- 需要跨组件/钩子共享数据 → Pinia
(如多个组件需要同步奖品列表) - 仅当前组件/逻辑复用 → 钩子
(如封装一个抽奖动画逻辑,无需共享数据) - Hooks适合封装逻辑,每次调用都是独立副本
- Store适合管理状态,全局共享同一数据源
在Vue3开发中,正确选择Hooks和Store的使用场景,能够避免数据污染和状态丢失问题,构建更健壮的应用架构。关键是要明确你的需求是 “逻辑复用” 还是 “状态共享”,这将直接决定你应该使用哪种方案。
误区
-
误以为钩子返回的数据会自动共享
// ❌ 错误预期:listA 和 listB 应该同步 const { list: listA } = useList(); const { list: listB } = useList();
-
在钩子内部用模块变量却未处理响应式
// ❌ 错误写法:直接修改模块变量可能丢失响应式 let moduleData = []; const useData = () => { const update = () => { moduleData = [1, 2, 3]; }; return { data: moduleData, update }; // data 不会更新! };