【问题标题】:Inferring generic function type in TypeScript在 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

【问题讨论】:

    标签: typescript types type-inference


    【解决方案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;
    };
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-17
    • 1970-01-01
    • 2016-12-05
    • 2020-07-23
    • 2017-10-04
    • 2016-11-01
    • 2020-01-23
    • 2017-06-02
    相关资源
    最近更新 更多