typescript - 如何使用 TypeScript 键入 Object 函数值的映射?
问题描述
我正在尝试通过组合减速器在 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 的地图和npm-ramda的高级类型。这几乎奏效了,除了任何选择器的返回结果是其“范围”内所有选择器的联合。
我在 StackBlitz 上建立了一个项目来演示这个问题。
解决方案
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);
}
Using{} as any
删除了reduce
函数中的任何类型安全性,但你一开始就没有任何类型安全性,所以我对此并不感到难过。
这是一个 StackBlitz,因此您可以看到它的实际效果:链接
推荐阅读
- regex - 如果字符串不包含破折号,则修改此 yup 验证以将最大长度更改为 9
- github - github标签在不同时间创建和推送,github标签上的时间是什么时候
- jquery - 将类添加到父 div,然后将其删除
- ruby-on-rails - 多个数据库上的rails 6 has_many关系
- vba - 是否可以通过更改记录来防止连续形式的文本框改变大小?
- wpf - 如何在wpf中创建拖放?
- dataframe - 在 Julia 中获取数据框的名称
- laravel - Laravel 单元测试自动依赖注入不起作用?
- python - 从列表中获取列总数
- entity-framework - 如何在 EF Core 3 查询中解析 int?