我试图通过组成reducer在monorepo应用程序中设置状态。目前,所有状态均按域划分,例如
type State = { fruit: FruitState, snacks: SnackState })
每个状态域都包含一些选择器。这些选择器是以封装方式定义的,例如
const selectApples = (state: FruitState) => state.apples;
然后我们有一个web
模块,该模块导入所有状态域选择器,按键对它们进行分组,然后将它们包装在高阶函数中,以将它们限制在域名称空间中,例如
function scopeSelector<T extends keyof State>(
scopeNamespace: T,
selectors: { [selector: string]: Function }
) {
return Object.keys(selectors).reduce((scoped, key) => ({
...scoped,
[key]: (state: State) => selectors[key](state[scopeNamespace])
}), {});
}
export const selectors = {
fruits: scopeSelector('fruits', fruits.selectors),
snacks: scopeSelector('snacks', snacks.selectors)
};
此代码在运行时有效-但会产生TypeScript错误,例如
// Error: Property 'selectApples' does not exist on type '{}'.
const apples = selectors.fruits.selectApples(state);
我尝试将Ramda's map与npm-ramda一起使用。 几乎起作用,除了任何选择器的返回结果是其“作用域”内所有选择器的并集。
我有set up a project on StackBlitz可以说明问题。
答案 0 :(得分:4)
TypeScript无法推断{ [K in keyof typeof selector]: (state: RootState) => ReturnType<typeof selector[K]> }
的返回类型。不幸的是,在使用reduce
来构建对象时,推断任何类型(甚至不需要转换就可以定义它们)几乎是不可能的。
也就是说,您可以通过强制转换并手动声明返回类型来获得所需的行为。
function scopeSelector<
T extends keyof RootState,
S extends { [selector: string]: (state: RootState[T]) => any }
>(
scopeNamespace: T,
selectors: S
): { [K in keyof S]: (state: RootState) => ReturnType<S[K]> } {
return Object.keys(selectors).reduce((scoped, key) => ({
...scoped,
[key]: (state: RootState) => selectors[key](state[scopeNamespace])
}), {} as any);
}
使用{} as any
会删除reduce
函数中的所有类型安全性,但是您一开始没有任何类型安全性,因此我对此并不感到遗憾。
这是一个StackBlitz,因此您可以看到它的运行情况:Link
答案 1 :(得分:2)
是的,可以!
类型定义
请考虑以下定义:
declare function scopeSelector<Namespace extends keyof RootState, SubSelectors extends Selectors<any, RootState[Namespace]>>(scope: Namespace, selectors: SubSelectors): Result<SubSelectors>;
位置:
type Selectors<K extends string = string, S = any> = {
[index in K]: (state: S) => ValueOf<S>;
}
type Result<T> = {
[K in keyof T]: (state: RootState) =>
T[K] extends AnyFunction
? ReturnType<T[K]>
: never;
}
type AnyFunction = (...args: any[]) => any;
type ValueOf<T> = T[keyof T];
实施
function scopeSelector<Namespace extends keyof RootState, SubSelectors extends Selectors<any, RootState[Namespace]>>(scope: Namespace, selectors: SubSelectors): Result<SubSelectors> {
return Object.keys(selectors)
.reduce<Result<SubSelectors>>(
(accumulator, current) => ({
...accumulator,
[current]: (state: RootState) => selectors[current](state[scope])
}),
{} as Result<SubSelectors>
)
}