在Typescript中映射的对象类型

时间:2017-10-23 18:08:24

标签: typescript

这是一个通用的打字稿问题,但我将根据我目前正在处理的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一样。我已经查看了打字稿中的映射类型,但到目前为止,我很难创建比简单选择或简单映射更复杂的东西。

是否有人提示如何处理此问题?

2 个答案:

答案 0 :(得分:2)

2017年10月的原始答案

从版本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!)
  };
}

只是一个想法。祝你好运!

2018年9月更新

嗯,自从提出这个问题以来,已经引入了一些关键功能,将其从“你不能真正做到这一点”转变为“你可以相当容易地做到这一点”:

那些涵盖了原始答案中缺少的功能。我们来用吧!我们就是这样做的:

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返回的相同参数的函数。 UnthunkedActionCreatorsUnthunkedActionCreator映射到对象上。让我们看看它是否有效:

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