【问题标题】:typescript util type that can change the return type of all function signatures/overloads to a new type可以将所有函数签名/重载的返回类型更改为新类型的 typescript util 类型
【发布时间】:2022-11-16 15:19:34
【问题描述】:

我在库中有一个类型,我想通过更改该类型中每个函数签名的返回类型来修改它。

interface Chainer<Subject> {  
  (chainer: 'be.a', type: string): Cypress.Chainable<Subject>
  (chainer: 'be.above', value: number | Date): Cypress.Chainable<Subject>
  // ... many many more overloads here
}

我想创建一个 util 类型,它可以将所有函数签名转换为具有不同的返回类型。

// This is the util that should change the return type
type RemapFunctionReturnType<T, U> = T extends (...args: infer P) => any ? (...args: P) => U : never;

type ModifiedChainer = RemapFunctionReturnType<Chainer<any>, number>;

// All return types changed to number
// interface ModifiedChainer<Subject> {  
//   (chainer: 'be.a', type: string): number; 
//   (chainer: 'be.above', value: number | Date): number;
//   // ... many many more overloads here
// }

需要帮助使所有签名的重映​​射函数返回类型递归。到目前为止,这是我尝试过的方法,但它仅适用于单个签名:

type RemapFunctionReturnType<T, U> = T extends (...args: infer P) => any & infer Next ? (...args: P) => U | RemapFunctionReturnType<Next, U> : never;

【问题讨论】:

  • 这是 TypeScript 的设计限制或缺失功能,请参阅ms/TS#29732。不能(轻易地)在类型系统中以编程方式操作重载;一般来说,您只会得到最后一个签名。这里的任何解决方法都必然只适用于一些任意选择的有限数量的重载,并且将涉及编写大小随该数量缩放的实用程序类型。不幸的是,如果您正在为单个类型执行此操作,您应该只复制类型定义并对其进行查找替换。
  • 这是否完全解决了您的问题?如果是这样,我可以写一个答案来解释(假设我没有找到重复的)。如果没有,我错过了什么? (如果您回复,请提及@jcalz 以通知我。)
  • 我看这很不幸。我一直在复制和修改这些类型,但一直在寻找一种不必重复工作的方法。

标签: typescript


【解决方案1】:

目前 TypeScript 的一个限制是 overloads 不能轻易地在类型系统中以编程方式操作。当你直接称呼一个重载函数,编译器将根据参数从列表中使用适当的调用签名来解析调用。

但是,当您尝试使用 genericsconditional types 来探测类型系统中的重载函数时,它们总是看起来好像只有一个调用签名(通常是列表中的最后一个)。 microsoft/TypeScript#29732 上有一个公开请求,要求像您的RemapFunctionReturnType 这样的条件类型返回更好的东西,但现在它不是语言的一部分。


我所知道的将重载类型梳理成其组成调用签名的有序列表的变通方法仅支持任意固定数量的调用签名。参见Parameters generic of overloaded function doesn't contain all optionsTypescript: ReturnType of overloaded function。以下内容适用于最多四个重载:

type Overloads<T> =
  T extends {
    (...args: infer A1): infer R1; (...args: infer A2): infer R2;
    (...args: infer A3): infer R3; (...args: infer A4): infer R4
  } ?
  [(...args: A1) => R1, (...args: A2) => R2, (...args: A3) => R3, (...args: A4) => R4] :
  T extends {
    (...args: infer A1): infer R1; (...args: infer A2): infer R2;
    (...args: infer A3): infer R3
  } ?
  [(...args: A1) => R1, (...args: A2) => R2, (...args: A3) => R3] :
  T extends {
    (...args: infer A1): infer R1; (...args: infer A2): infer R2
  } ?
  [(...args: A1) => R1, (...args: A2) => R2] :
  T extends {
    (...args: infer A1): infer R1
  } ?
  [(...args: A1) => R1] :
  any

你看到这个定义的大小随着正方形您希望能够支持的重载数量。有了一个“足够大”的Overloads 助手,你可以这样写:

type FromOverloads<T extends readonly any[]> =
    { [K in keyof T]: (x: T[K]) => void }[number] extends 
    (x: infer U) => void ? U : never;

type ModifyReturn<T extends readonly ((...args: any) => any)[], U> =
    { [K in keyof T]: T[K] extends (...args: infer A) => void ?   
    (...args: A) => U : never };

这将为您提供所需的类型(TypeScript 中的 intersection 函数等同于重载函数):

type ModifiedChainer = FromOverloads<ModifyReturn<Overloads<Chainer>, number>>
/* type ModifiedChainer = 
     ((chainer: "be.a", type: string) => number) & 
     ((chainer: "be.above", value: number | Date) => number) 
*/

这很好,但是如果您只是为了生成 ModifiedChainer 而编写 Overloads&lt;T&gt;,那么您必须进行大量额外的手动工作以避免中等数量的额外手动工作。所以这几乎肯定不值得。

直到并且除非像 microsoft/TypeScript#29732 这样的东西被实现,你还不如找到 Chainer 的定义并在 IDE 中进行手动查找和替换,并提醒自己检查上游对原始文件的更改Chainer类型。

Playground link to code

【讨论】:

    猜你喜欢
    • 2019-08-11
    • 2021-05-17
    • 2021-10-06
    • 2018-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-03
    相关资源
    最近更新 更多