[英]Mapped object types in Typescript
这是一个普通的打字稿问题,但我将根据目前正在使用的react-redux应用程序进行说明。 如有必要,我可以举一个一般的例子,但希望现在就足够了。 我正在尝试将redux-thunk反应应用程序中的actionCreators映射到已经分配了dispatch和getState的类型中。 我有:
export interface AppThunkAction<TAction= {}, TDispatchResponse = void, TActionResponse = void> {
(dispatch: (action: TAction) => TDispatchResponse, getState: () => ApplicationState): TActionResponse;
}
我的动作创作者看起来像这样:
@typeName('BAR_ACTION')
export class BarAction extends Action {
constructor() {
super();
}
}
@typeName('FOO_ACTION')
export class FooAction extends Action {
constructor(public baz: string) {
super();
}
}
export const actionCreators = {
foo: (baz: string): AppThunkAction<FooAction, void, Promise<void>> => (dispatch, getState): Promise<void> => {
return new Promise((resolve, reject) =>
resolve(dispatch(new FooAction(baz)))
);
},
bar: (): AppThunkAction<BarAction, void, Promise<number>> => (dispatch, getState): Promise<number> => {
return new Promise((resolve, reject) => {
dispatch(new BarAction());
resolve(3);
});
},
};
这些对象由redux-thunk中间件处理,因此实际类型(我需要为组件指定)看起来像这样
export type actionCreatorsTypes = {
foo: (baz: string) => Promise<void>,
bar: () => Promise<number>,
};
问题是我必须手动编写这些类型定义,如果我忘记更改它们,打错字等,事情就会变得混乱。
因此,我正在寻找自动化的方式来处理此问题。 一个函数或修饰器,它将在此类型上进行迭代并进行修改,就像注入了dispatch和getState一样。 我已经看过打字稿中的映射类型,但到目前为止,我仍在努力创建比简单选择或简单映射更复杂的东西。
有人对如何解决这个问题有暗示吗?
从版本2.5开始,TypeScript不支持使用函数类型进行类型操作:
缺少可变参数类型意味着您无法对不同数量的参数的函数进行概括。 在您的情况下,没有一种简单的方法可以写出foo
属性将接受单个string
参数,而bar
属性则不接受参数,从而与泛型很好地映射。
您也不能采用任意函数类型并以编程方式提取其参数或返回类型,就像使用keyof
和lookup类型使用对象属性keyof
。 有一个建议可以为您提供这种针对函数类型的功能,但目前尚不存在。
最后,您不能以没有将诸如映射条件类型之类的东西将AppThunkAction<T, D, R>
为R
的方式来任意解开类型 。
每种方法都有解决方法,但是它们比仅以您现在的方式手动指定函数类型更丑陋/笨拙/维护性较差。 我已经沿着那条路线走了好几次,很少值得这么做。
因此,这意味着从概念上讲简单的操作“采用任何函数类型(a:A, b:B, ...)=>AppThunkAction<T, D, R>
并产生相关函数类型(a: A, b: B, ...)=>R
“不能真正完成。 抱歉。 🙁
我确实知道手动指定类型是多余的,但是如果您进行一些测试代码,则在更改类型时至少应该捕获错误:
if (false as true) {
// errors will appear in here if you leave out keys or change signatures
const act: actionCreatorsTypes = {
foo: (x) => actionCreators.foo(x)(null!, null!),
bar: () => actionCreators.bar()(null!, null!)
};
}
只是一个想法。 祝好运!
好吧,由于提出了这个问题,所以引入了一些关键功能,这些功能从“您不能真正做到”变为“您可以很容易做到”:
TypeScript 2.8 使用infer
关键字为我们提供了条件类型和类型推断 ,使我们能够从函数签名中拉出参数并返回类型,并将AppThunkAction<T, D, R>
为R
TypeScript 3.0使我们能够在rest / spread表达式中使用元组,以一种通用的方式表示“函数参数列表”的类型。 这是支持可变参数类型的很大一部分,对于我们这里的目的来说已经足够了。
这些覆盖了原始答案中缺少的功能。 让我们使用它们! 这是我们的方法:
type UnthunkedActionCreator<T> =
T extends (...args: infer A) => AppThunkAction<any, any, infer R> ? (...args: A) => R : never;
type UnthunkedActionCreators<O extends Record<keyof O, (...args: any[]) => AppThunkAction<any, any, any>>> = {
[K in keyof O]: UnthunkedActionCreator<O[K]>
}
UnthunkedActionCreator
将返回AppThunkAction
的函数转换为具有相同参数的函数,该函数返回AppThunkAction
返回的内容。 而UnthunkedActionCreators
地图UnthunkedActionCreator
过的对象。 让我们看看它是否有效:
type ActionCreatorsType = UnthunkedActionCreators<typeof actionCreators>;
检查为
// type ActionCreatorsType = {
// foo: (baz: string) => Promise<void>;
// bar: () => Promise<number>;
// }
看起来不错。 看,我们要做的就是等待一年。 干杯!
现在可以实现(从3.0版开始),下面是代码:
type GetActionCreatorType<FF extends (...args: any[]) => (...args: any[]) => any> =
FF extends (...args: infer A) => (...args: infer _) => infer R ? (...args: A) => R
: never;
type GetActionCreatorsTypes<T extends {[key: string]: (...args: any[]) => (...args:
any[]) => any}> = {
[P in keyof T]: GetActionCreatorType<T[P]>;
};
您可以在单个功能上使用第一个功能,而第二个功能可以在动作创建者的对象上使用。
我的答案是基于这个答案
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.