Typescript:定义为元组的扩展函数参数

Typescript:定义为元组的扩展函数参数,typescript,Typescript,我有一个Typescript项目,我在其中调用函数内部的Date。我想使用与重载的Date中的构造函数相同的函数参数: interface DateConstructor { new(): Date; new(value: number | string | Date): Date; new(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: num

我有一个Typescript项目,我在其中调用函数内部的
Date
。我想使用与重载的
Date
中的构造函数相同的函数参数:

interface DateConstructor {
    new(): Date;
    new(value: number | string | Date): Date;
    new(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): Date;
    // ...
}
我想使用spread运算符将参数传递给
Date
的构造函数。所以我的函数应该是这样的:

function myFn(...args: DateComponents): Date {
    return new Date(...args)
}
在我的案例中,我并没有实际返回日期,但这只是为了测试

现在的问题是如何定义
DateComponents
。我成功地分别实现了
DateConstructor
的3个重载签名

// Case A: No argument provided
interface A {
  (): Date;
}
const a: A = function (...args: []): Date {
  return new Date(...args);
}

// Case B: 1 argument provided
interface B {
  (value: number | string | Date): void;
}
const b: B = function (...args: [number | string | Date]): Date {
  return new Date(...args);
}

// Case C: Between 2 and 7 arguments provided
interface C {
  (year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): void;
}
const c: C = function (...args: [number, number, number?, number?, number?, number?, number?]): Date {
  return new Date(...args);
}
到目前为止一切正常

  • A:空元组用于覆盖未传递参数的情况
  • B:带有一个元素的元组包含传递单个数字、字符串或
    Date
    对象的情况
  • C:包含2个强制和5个可选数字元素的元组涵盖了传递2-7个参数的情况
如果我尝试将这3个示例合并为1,我会得到以下结果:

interface D {
  (): void;
  (value: number | string | Date): void;
  (year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): void;
}
type DateComponents = [] | [number | string | Date] | [number, number, number?, number?, number?, number?, number?];
const d: D = function (...args: DateComponents): Date {
  return new Date(...args); // <-- ERROR: Expected 0-7 arguments, but got 0 or more.
}
接口D{
():无效;
(值:数字|字符串|日期):无效;
(年:数,月:数,日期?:数,小时?:数,分钟?:数,秒?:数,毫秒?:数):无效;
}
键入DateComponents=[]|[number | string | Date]|[number,number,number?,number?,number?,number?,number?];
常量d:d=函数(…参数:日期组件):日期{

返回新日期(…args);//根本原因是TypeScript不支持同时使用多个调用/构造签名解析对重载函数/构造函数的调用。调用<代码>新日期(…args);
是一个单独的调用,但为了让它被接受,编译器必须将
DateComponents
分解为它的联合成员,并查看每个成员至少可分配一个构造签名。相反,它看到没有单个
Date
构造签名适用于整个
DateComponents
请注意,您看到的错误的具体措辞有点像是在转移注意力;编译器无法接受输入,并试图将其硬塞进有关参数数量的可用错误消息中。这在以前发生过(例如),但似乎不是需要解决的重要问题

无论如何,GitHub中有一个(相当长期的)开放特性请求,要求重载函数接受参数的并集;请参阅。目前还不清楚这种情况是否会发生


那么,你能做什么呢?最简单的方法就是使用:

这也不好看


或者,您可以尝试构建一些代码,将重载构造函数转换为采用联合类型参数的非重载构造函数。也就是说,。如果这样做,调用将如下所示:

const f: D = function (...args: DateComponents): Date {
  return new (unifyConstructorOverloads(Date))(...args);
}
type UnifyConstructorOverloads<T extends new (...args: any) => any> =
  new (...args: ConstructorParameters<ConstructorOverloads<T>[number]>) =>
    InstanceType<ConstructorOverloads<T>[number]>;

const unifyConstructorOverloads = <T extends new (...args: any) => any>(f: T) => f as
  UnifyConstructorOverloads<T>;
这本身并不难看。但是
unifyConstructorOverloads
的定义如下:

const f: D = function (...args: DateComponents): Date {
  return new (unifyConstructorOverloads(Date))(...args);
}
type UnifyConstructorOverloads<T extends new (...args: any) => any> =
  new (...args: ConstructorParameters<ConstructorOverloads<T>[number]>) =>
    InstanceType<ConstructorOverloads<T>[number]>;

const unifyConstructorOverloads = <T extends new (...args: any) => any>(f: T) => f as
  UnifyConstructorOverloads<T>;
type unifyconstructor重载any>=
新建(…参数:构造函数参数)=>
实例类型;
const unifyConstructorOverloads=any>(f:T)=>f as
UnifyConstructor重载;
它使用类型断言,依赖于
ConstructorOverloads
的定义,这是一个假设的类型函数,它接受一个重载的构造函数,并将其多个构造签名分离到一个元组中。据我所知,没有编程的方法来实现这一点,所以你必须模拟到一些重载(例如,5):

类型构造函数重载=
T延伸{
新(…参数:推断A1):推断R1;新(…参数:推断A2):推断R2;
新(…参数:推断A3):推断R3;新(…参数:推断A4):推断R4;
新(…参数:推断A5):推断R5;
} ? [
新(…参数:A1)=>R1,新(…参数:A2)=>R2,
新(…参数:A3)=>R3,新(…参数:A4)=>R4,
新(…参数:A5)=>R5
]:T扩展{
新(…参数:推断A1):推断R1;新(…参数:推断A2):推断R2;
新建(…参数:推断A3):推断R3;新建(…参数:推断A4):推断R4
} ? [
新(…参数:A1)=>R1,新(…参数:A2)=>R2,
新(…参数:A3)=>R3,新(…参数:A4)=>R4
]:T扩展{
新(…参数:推断A1):推断R1;新(…参数:推断A2):推断R2;
新(…参数:推断A3):推断R3
} ? [
新(…参数:A1)=>R1,新(…参数:A2)=>R2,
新(…参数:A3)=>R3
]:T扩展{
新(…参数:推断A1):推断R1;新(…参数:推断A2):推断R2
} ? [
新(…参数:A1)=>R1,新(…参数:A2)=>R2
]:T扩展{
新(…参数:推断A1):推断R1
} ? [
新(…参数:A1)=>R1
]:任何
就美学而言,这已经远远超出了“丑陋”的范畴,可能是“怪诞”的范畴。如果你打算在代码库的许多地方做这种重载统一的事情,我可以想象关闭
ConstructorOverloads
(对于常规函数,类似的
重载
,请参见该代码)进入一个没有灯光的图书馆,这样你就可以使用它,而不必直视它丑陋的面容


但如果您只做了几次,我强烈建议您使用类型断言,然后继续


类型断言是最简单的解决方案感谢jcalz的出色回答。误导性的错误消息使我认为简单的解决方案是可能的。我将使用类型断言,因为替代方案是……正如您所说……怪诞:)
type ConstructorOverloads<T> =
  T extends {
    new(...args: infer A1): infer R1; new(...args: infer A2): infer R2;
    new(...args: infer A3): infer R3; new(...args: infer A4): infer R4;
    new(...args: infer A5): infer R5;
  } ? [
    new (...args: A1) => R1, new (...args: A2) => R2,
    new (...args: A3) => R3, new (...args: A4) => R4,
    new (...args: A5) => R5
  ] : T extends {
    new(...args: infer A1): infer R1; new(...args: infer A2): infer R2;
    new(...args: infer A3): infer R3; new(...args: infer A4): infer R4
  } ? [
    new (...args: A1) => R1, new (...args: A2) => R2,
    new (...args: A3) => R3, new (...args: A4) => R4
  ] : T extends {
    new(...args: infer A1): infer R1; new(...args: infer A2): infer R2;
    new(...args: infer A3): infer R3
  } ? [
    new (...args: A1) => R1, new (...args: A2) => R2,
    new (...args: A3) => R3
  ] : T extends {
    new(...args: infer A1): infer R1; new(...args: infer A2): infer R2
  } ? [
    new (...args: A1) => R1, new (...args: A2) => R2
  ] : T extends {
    new(...args: infer A1): infer R1
  } ? [
    new (...args: A1) => R1
  ] : any