如何为Pinia通用组合式函数正确实现类型定义?
解决Pinia组合式函数中的TypeScript索引类型错误
上下文环境
- VueJs 3.4.19
- Typescript 5.3.3
- Pinia 2.1.6
原始场景
在多个视图组件中,需要在onMounted钩子中调用Pinia store的异步action获取数据,示例代码如下:
// Users.vue import { useUserStore } from '@/store/userStore'; import { onMounted } from 'vue'; const userStore = useUserStore(); onMounted(async() => { try { await userStore.fetchUsers(); } catch (err) { // 错误处理逻辑,如404等 } });
重构尝试
为了复用逻辑,创建了通用组合式函数useModelLoader,但遇到TypeScript类型错误:
// useModelLoader.ts import { StoreDefinition, StateTree, _GettersTree, _ActionsTree } from "pinia"; export const useModelLoader = < ID extends string, S extends StateTree, G extends _GettersTree<S>, A extends _ActionsTree, >(storeDef: StoreDefinition<ID, S, G, A>, storeModel: keyof S, storeAction: keyof A) => { const store = storeDef(); // TS错误:Type 'keyof S' cannot be used to index type 'Store<ID, S, G, A>'.ts(2536) const model = store[storeModel]; // TS错误:Type 'keyof A' cannot be used to index type 'Store<ID, S, G, A>'.ts(2536) const action = await store[storeAction]; };
调用示例
// Users.vue import { useUserStore } from '@/store/userStore'; const modelLoader = useModelLoader(useUserStore, 'users', 'fetchUsers');
现有正常表现
- 若
useModelLoader第一个参数不是StoreDefinition,TS报错; - 若第二个参数不是store state中存在的key,TS报错;
- 若第三个参数不是store actions中存在的action,TS报错;
- 运行时打印
store[model]能显示正确数据。
存在的问题
- 组合式函数中
model变量出现TS索引错误; - 组合式函数中
action变量出现TS索引错误; - 原代码中
await store[storeAction]逻辑错误,未调用action函数。
解决方案
核心原因
Pinia的Store实例类型是State、Getters、Actions的交叉类型,但TypeScript无法自动将keyof S(state的键)、keyof A(action的键)与Store的索引类型关联,需要通过类型断言明确合法性,同时修正action调用逻辑。
修正后的组合式函数
// useModelLoader.ts import { StoreDefinition, StateTree, _GettersTree, _ActionsTree, Store } from "pinia"; export const useModelLoader = async < ID extends string, S extends StateTree, G extends _GettersTree<S>, A extends _ActionsTree, >(storeDef: StoreDefinition<ID, S, G, A>, storeModel: keyof S, storeAction: keyof A) => { const store = storeDef() as Store<ID, S, G, A>; // 断言state键合法,并指定类型为对应state值的类型 const model = store[storeModel as keyof typeof store] as S[keyof S]; // 断言action键合法,指定类型为对应action函数的类型,调用后await const action = store[storeAction as keyof typeof store] as A[keyof A]; await action(); // 返回模型数据和store实例,方便后续使用 return { model, store }; };
优化:约束action为异步函数
为了更安全,可以添加泛型约束,确保传入的action是返回Promise的异步函数:
export const useModelLoader = async < ID extends string, S extends StateTree, G extends _GettersTree<S>, A extends Record<string, (...args: any[]) => Promise<any>>, // 约束action为异步函数 >(storeDef: StoreDefinition<ID, S, G, A>, storeModel: keyof S, storeAction: keyof A) => { const store = storeDef(); const model = store[storeModel as keyof typeof store] as S[keyof S]; const action = store[storeAction as keyof typeof store] as A[keyof A]; await action(); return { model, store }; };
正确调用方式
// Users.vue import { useUserStore } from '@/store/userStore'; import { onMounted } from 'vue'; onMounted(async () => { try { const { model: users } = await useModelLoader(useUserStore, 'users', 'fetchUsers'); // 直接使用users数据 } catch (err) { // 错误处理逻辑 } });
内容的提问来源于stack exchange,提问作者Vins
相关产品推荐
相关产品推荐

