我使用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[]
),只会覆盖第一个参数作为全局存储状态。
有没有更好的方法来处理这样的嵌套泛型?这甚至可能吗?
答案 0 :(得分:2)
您需要为IScopedSelectors
和INestedScopedSelectors
的通用类型添加类似的类型约束。
如果没有这样的约束,您已经告诉TypeScript T
中的IScopedSelectors<T>
可以是任何类型。所以IScopedSelectors<string>
应该有效。但TypeScript正确地指出,对于许多类型(例如string
),[K in keyof T]: IScopedSelector<T[K]>
不会起作用,因为无法保证T[K]
遵守{{1}强加的约束}}
所以解决方案只是为两个接口添加一个约束,以便TypeScript具有这种保证。为此,内置的IScopedSelector
类型可能会有所帮助。如下所示:
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.
};
类型替换为更具体的类型,但解决方案基本相同。只需确保在层次结构中一直转发约束。
希望有所帮助!