在 TypeScript 中推断泛型函数类型

Posted

技术标签:

【中文标题】在 TypeScript 中推断泛型函数类型【英文标题】:Inferring generic function type in TypeScript 【发布时间】:2020-09-09 08:39:12 【问题描述】:

我正在创建一个函数来创建 Redux 操作(例如来自 redux 工具包的 createAction)。我需要一个返回动作生成器的函数,我希望这个生成器是通用的,基于提供给创建者函数的类型。

const createGenericAction = <T extends string>(type: T) => <
  A extends ,
  B extends 
>(
  payloadGenerator: (a: A) => B
) => 
  const factory = (payload: A) => (
    type,
    payload: payloadGenerator(payload),
  );
  factory.toString = (() => type) as () => T;

  return factory;
;

这就是 creator 函数现在的样子(toString 的实现是由于与 redux-toolkit 的兼容性)。

payloadGenerator 不是泛型时可以正常工作,所以:

const someAction = createGenericAction('someAction')(
  (payload:  a: number; b: string ) => payload
);

类型正确。

虽然,当payloadGenerator 是泛型时,整个类型推断就会分崩离析:

const someAction = createGenericAction('someAction')(
  <T extends string>(payload:  value: T ) => payload
);
Argument of type '<T extends string>(payload:  value: T; ) =>  value: T; ' is not assignable to parameter of type '(a: ) =>  value: string; '.
  Types of parameters 'payload' and 'a' are incompatible.
    Property 'value' is missing in type '' but required in type ' value: string; '.ts(2345)

更复杂的例子

enum Element 
  Elem1 = 'elem1',
  Elem2 = 'elem2',


type ElementValueMapper = 
  [Element.Elem1]: string;
  [Element.Elem2]: number;
;

const someAction = createGenericAction('someAction')(
  <T extends Element>(payload:  e: T; value: ElementValueMapper[T] ) =>
    payload
);

这样的操作应该允许调用:

someAction( e: Element.Elem1, value: 'string' ); // okay
someAction( e: Element.Elem2, value: 5 ); // okay

但不允许:

someAction( e: Element.Elem1, value: 5 ); // error value should be type string

【问题讨论】:

【参考方案1】:

好的,我发现这个问题来自重新实现 Function 的 toString。这个想法是在将函数分配给toString之后,factory不再被视为“TypeScript函数”,而是如下接口:


  (payload: A): 
    payload: B;
  ;
  toString(): A;

并且这个界面的一部分是正确的,但是factory的整个类型不是我想要的。实际上,JS 中的每个函数(TS 中也是如此)都是function 类型,并且来自Function 原型,其中确实包含toString,但是TypeScript 确实区分了这些类型并且Function 并不经常使用。

没有太多的题外话,解决办法是断言类型为factory

const createGenericAction = <A extends string>(code: A) => <
  A extends ,
  B extends 
>(
  payloadGenerator: (a: A) => B
) => 
  const factory: (payload: A) =>  payload: B  = (payload: A) => (
    payload: payloadGenerator(payload),
  );
  factory.toString = () => code;

  return factory;
;

【讨论】:

我很好奇 redux-toolkit 如何解决这个问题,即使 @TomaszBłachut 它没有,github.com/reduxjs/redux-toolkit/issues/584

以上是关于在 TypeScript 中推断泛型函数类型的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript教程# 15:泛型

TypeScript 中泛型类型的子类型推断

如何推断泛型函数的返回类型

typescript 从子类调用的构造函数参数推断类型

TypeScript 中每个键的泛型映射对象类型

模板与泛型编程——模板实参推断