这是一个通用的打字稿问题,但我将根据我目前正在处理的react-redux应用程序进行解释。如果有必要,我可以提出一般的例子,但希望现在就足够了。 我试图将redux-thunk react应用程序中的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一样。我已经查看了打字稿中的映射类型,但到目前为止,我很难创建比简单选择或简单映射更复杂的东西。
是否有人提示如何处理此问题?
答案 0 :(得分:2)
从版本2.5开始,TypeScript对使用函数类型进行类型操作没有很大的支持:
缺少variadic kinds意味着您无法概括不同数量的参数的函数。在您的情况下,没有简单的方法可以写foo
属性将采用单个string
参数,而bar
属性不采用任何参数,其方式可以很好地映射泛型。
您也不能采用任意函数类型并以编程方式提取其参数或返回类型,就像使用keyof
and lookup types对象属性一样。有一个proposal可以为你提供这种函数类型的能力,但它还没有。
最后,您不能以AppThunkAction<T, D, R>
转换为R
而不使用mapped conditional types之类的方式随意展开类型。
每种方法都存在变通方法,但它们比你现在正在手动指定函数类型更丑陋/更笨拙/更少维护。我已经走了几次这条路,这很少值得。
这意味着“采取任何函数类型(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为我们提供了conditional types和type inference using the infer
keyword,允许我们从功能签名中提取参数和返回类型,以及将AppThunkAction<T, D, R>
转换为R
TypeScript 3.0使我们能够使用tuples in rest/spread expressions,以通用方式表示“函数参数列表”的类型。它是支持可变参数类型的很好的一部分,足以满足我们的目的。
那些涵盖了原始答案中缺少的功能。我们来用吧!我们就是这样做的:
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>;
// }
看起来不错。看,我们所要做的就是等一年。干杯!
答案 1 :(得分:0)
现在可以(从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]>;
};
您可以在单个功能上使用第一个功能,而第二个功能可以在动作创建者的对象上使用。
我的答案基于this answer