javascript - 在 TypeScript 中封装嵌套的函数对象?
我正在使用 Redux,并已将存储数据拆分为“切片”。每个“切片”对应一个区域,例如 users 或 comments。我将每个切片的选择器组合成一个顶层 selectors 模块,并在整个应用程序中访问它。
一个切片的格式是:
export interface IUsersSelectors {
getCurrentUser(state: IUsersState): IUser | undefined;
}
const selectors: IUsersSelectors = {
getCurrentUser(state: IUsersState) {
return state.currentUser;
}
};
export default {
getInitialState,
reducer,
selectors
};
然后全部导入,并将选择器组合起来:
export const selectors = Object.keys(slices).reduce((combinedSelectors: any, sliceKey: string) => {
const sliceSelectors = slices[sliceKey].selectors;
combinedSelectors[sliceKey] = Object.keys(sliceSelectors).reduce((selectorsMap: object, selectorKey: string) => {
const localizedSelector = sliceSelectors[selectorKey];
selectorsMap[selectorKey] = (globalState, ...args: any[]): any => {
return localizedSelector(globalState[sliceKey], ...args);
};
return selectorsMap;
}, {});
return combinedSelectors;
}, {});
然后在整个应用中使用:
selectors.users.getCurrentUser(store.getState());
这意味着选择器 期望 在获取数据时只使用它们的片段状态,但它们实际上是用全局存储状态被 调用 的。我本质上只是将它们包装在另一个管理作用域的函数中。
我接近于为此定义泛型类型的是:
type IScopedSelector<T extends () => any> = (globalState: IStoreState, ...args: any[]) => ReturnType<T>;
type IScopedSelectors<T> = {
[K in keyof T]: IScopedSelector<T[K]>;
};
type INestedScopedSelectors<R> = {
[S in keyof R]: IScopedSelectors<R[S]>;
};
export const selectors: INestedScopedSelectors<ISelectors>...
其中 ISelectors 是一个形状如下的简单接口:
export interface ISelectors {
users: IUsersSelectors;
}
但是,当尝试将 T[K] 传递给 IScopedSelector 时,我会遇到这个类型错误,因为它必须是一个函数:
[ts] Type 'T[K]' does not satisfy the constraint '() => any'.
如果我移除 extends() => any,那么我会得到关于 ReturnType 的错误:
[ts] Type 'T' does not satisfy the constraint '(...args: any[]) => any'.
理想情况下,我也会保持选择器参数的类型(而不是 ...args: any[]),只将第一个参数重写为全局存储状态。
有更好的方式来处理像这样的嵌套泛型吗?这甚至可能吗?
答案#1
你需要为 IScopedSelectors 和 INestedScopedSelectors 的泛型类型添加类似的类型约束。
没有这样的约束,你就告诉 TypeScript,在 IScopedSelectors<T> 中的 T 可以是任何类型。因此 IScopedSelectors<string> 应该是有效的。但 TypeScript 正确地指出,对于许多类型(例如 string),[K in keyof T]: IScopedSelector<T[K]> 将不起作用,因为无法保证 T[K] 遵循 IScopedSelector 施加的约束。
所以解决方案就是在两个接口上都添加一个约束,以便 TypeScript 拥有这个保证。为此,内置的 Record 类型可能会很有帮助。所以类似这样:
type IScopedSelectors<T extends Record<string, () => any>> = {
[K in keyof T]: IScopedSelector<T[K]>; // T[K] is now guaranteed to adhere to () => any
};
type INestedScopedSelectors<R extends Record<string, Record<string, () => any>>> = {
[S in keyof R]: IScopedSelectors<R[S]>; // Similarly, R[S] is now guaranteed to adhere to Record<string, () => any>, exactly what IScopedSelectors is expecting.
};
您可能想根据您的具体使用情况将 Record 类型替换为更具体的类型,但解决方案本质上是一样的。只需确保将您的约束向上完全传递到整个层级结构。
希望这有帮助!
