有没有办法从重载的 TS 方法中提取所有函数签名?

Posted

技术标签:

【中文标题】有没有办法从重载的 TS 方法中提取所有函数签名?【英文标题】:Is there any way to extract all of the function signatures from an overloaded TS method? 【发布时间】:2021-12-27 16:29:58 【问题描述】:

基本上我只是想包装一个有多个签名的函数。是否有任何干净的方法可以在不重新硬编码所有签名的情况下做到这一点?我只希望下面的bar 接受与foo 相同的任何参数

declare function foo(x: string): number;
declare function foo(x: string, y: number): 42;
declare function foo(x: number): string;

type fooArgs = Parameters<typeof foo>; // this only extracts from the last signature
declare function bar(...args: fooArgs): string[]

const a = foo(123);        // fine
const b = foo('123');      // fine
const c = foo('123', 456); // fine

const x = bar(123);        // fine
const y = bar('123');      // fails because it expects (x: number) => string
const z = bar('123', 456); // fails

ts playground

【问题讨论】:

不可能。看到这个问题github.com/microsoft/TypeScript/issues/28789 【参考方案1】:

这是设计使然。它总是返回最后一个重载的签名。看到这个issue/28789

但是,还有另一种方法。

请记住,重载只是函数类型的交集。

我们可以声明一个类型来保存我们所有的重载签名:


type Signatures = 
    1: (x: string) => number,
    2: (x: string, y: number) => 42
    3: (x: number) => string

现在,为了创建重载,我们需要获取所有对象属性的并集并将它们相交。


// credits goes to https://***.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;

type Values<T> = T[keyof T]

type Overloading = UnionToIntersection<Values<Signatures>>

它按预期工作:

declare const foo: Overloading;

const a = foo(123); // string
const b = foo('123'); // number
const c = foo('123', 456); // 42

现在,很容易获得所有允许参数的并集:


//  [x: string] | [x: string, y: number] | [x: number]
type fooArgs = Parameters<Values<Signatures>>;
declare function bar(...args: fooArgs): string[]


const x = bar(123); // string[]
const y = bar('123'); // string[]
const z = bar('123', 456); // string[]

完整代码:



type Signatures = 
    1: (x: string) => number,
    2: (x: string, y: number) => 42
    3: (x: number) => string


// credits goes to https://***.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;

type Values<T> = T[keyof T]

type Overloading = UnionToIntersection<Values<Signatures>>

declare const foo: Overloading;

const a = foo(123); // string
const b = foo('123'); // number
const c = foo('123', 456); // 42


//  [x: string] | [x: string, y: number] | [x: number]
type fooArgs = Parameters<Values<Signatures>>;
declare function bar(...args: fooArgs): string[]


const x = bar(123); // string[]
const y = bar('123'); // string[]
const z = bar('123', 456); // string[]

Playground

【讨论】:

哈哈,这太棒了。不幸的是,在这种情况下,我不控制源文件,所以它仍然会重新硬编码所有签名。不过,将来会在其他情况下记住这一点! 谢谢。请注意,有一个缺点。如果你有泛型,它就不能很好地工作,就像这里***.com/questions/65508351/…【参考方案2】:

我发现这个问题是因为我想要一些相同的结果,但是对于我懒得自己编写的一堆可链接的写入方法。

这就是我从captain-yossarian's answer 实现UnionToIntersection 类型的方式:

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

// create accessor signature type. Generics are just for method params & return type.
type AccessorSignature<Self, In, Out = In> = 
  get(): Out;
  set(value: In): Self;
;

// apply union to intersection on accessor signature
type Accessor<Self, In, Out = In> = UnionToIntersection<
  AccessorSignature<Self, In, Out>[keyof AccessorSignature<Self, In, Out>]
>;

// create a class with the typed chainable accessors
declare class YeetProps 
  foo: Accessor<Yeet, string | number, string>;
  bar: Accessor<Yeet, string>;
  baz: Accessor<Yeet, boolean>;


// implement it in my final class
class Yeet extends YeetProps 

const fooValue = new Yeet().foo(); // read string value

// chain write
new Yeet().foo("woop!").bar("this is amazing").baz(true);

我最终会使用Proxy 来实际实现这些方法。

【讨论】:

以上是关于有没有办法从重载的 TS 方法中提取所有函数签名?的主要内容,如果未能解决你的问题,请参考以下文章

从派生类调用重载函数

从两个角度理解为什么 JS 中没有函数重载

TS 函数重载

应用程序身份验证 - 从命令行提取公共签名密钥

从 php 中的 PKCS7 签名中提取证书

假设我不使用任何重载函数,有没有办法可以阻止所有名称修改? [复制]