【问题标题】:Mapped object types in TypescriptTypescript 中的映射对象类型
【发布时间】:2018-04-04 09:11:50
【问题描述】:

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

有人对如何处理这个问题有提示吗?

【问题讨论】:

  • 在我看来类似 CreateTypes = { [P in keyof T]: T[P]; };类型 c = CreateTypes;正在朝着正确的方向前进。我只需要找出需要用什么来代替 T[P];为了修改该属性的类型
  • 现在可以使用 TypeScript 3,请参阅:stackoverflow.com/a/52187113/2844025
  • 太棒了,谢谢你告诉我!我会调查的。

标签: typescript


【解决方案1】:

原始答案,2017 年 10 月

TypeScript 2.5 版对使用函数类型进行类型操作没有很好的支持:

  • 缺少variadic kinds 意味着您无法概括不同数量参数的函数。在您的情况下,没有简单的方法可以写出 foo 属性将采用单个 string 参数,而 bar 属性不采用任何参数,从而可以很好地与泛型映射。

  • 您也不能采用任意函数类型并以编程方式提取其参数或返回类型,就像使用 keyof and lookup types 处理对象属性的方式一样。有一个 proposal 可以为您提供函数类型的这种能力,但它还没有。

  • 1234563

每种方法都存在变通方法,但它们比现在手动指定函数类型更难看/更笨拙/更难维护。我已经沿着这条路线走了好几次,但几乎不值得。

这意味着“获取任何函数类型(a:A, b:B, ...)=&gt;AppThunkAction&lt;T, D, R&gt;并产生相关的函数类型(a: A, b: B, ...)=&gt;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 月更新

好吧,既然问了这个问题,我们就引入了一些关键特性,这些特性从“你不能真正做到这一点”变为“你可以相当容易地做到这一点”:

  • TypeScript 2.8 为我们提供了 conditional typestype inference using the infer keyword,允许我们从函数签名中提取参数和返回类型,以及将 AppThunkAction&lt;T, D, R&gt; 转换为 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 返回的相同参数的函数。而UnthunkedActionCreatorsUnthunkedActionCreator 映射到一个对象上。让我们看看它是否有效:

type ActionCreatorsType = UnthunkedActionCreators<typeof actionCreators>;

检查为

// type ActionCreatorsType = {
//   foo: (baz: string) => Promise<void>;
//   bar: () => Promise<number>;
// }

看起来不错。看,我们所要做的就是等一年。干杯!

【讨论】:

  • 哇,知识渊博的答案,谢谢!像这样的事情目前还没有实施,真是太遗憾了。我虽然更多的人会遇到像我这样的问题。关于测试的好主意,我想这至少是一些东西。我仍然希望有一天这将成为可能
  • 已更新以使用新功能
【解决方案2】:

现在可以实现(从 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

【讨论】:

  • 我很乐意更新我的答案。哦好吧?‍♀️
  • 啊,当然,如果你愿意,那就去吧。您的回答非常有帮助,因此重新接受它才是公平的;)
  • 好吧,你要的!
猜你喜欢
  • 2021-03-28
  • 2020-11-25
  • 2018-05-06
  • 2020-03-30
  • 1970-01-01
  • 1970-01-01
  • 2021-10-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多