简体   繁体   English

打字稿:定义为元组的传播函数参数

[英]Typescript: Spreading function parameters defined as tuples

I have a Typescript project where I'm calling Date inside of a function.我有一个 Typescript 项目,我在函数内部调用Date I want to use the same function parameters as the constructor from Date which is overloaded:我想使用与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;
    // ...
}

I would like to use the spread operator in order to pass the parameters to the constructor of Date .我想使用扩展运算符将参数传递给Date的构造函数。 So my function should hopefully look like that:所以我的函数应该是这样的:

function myFn(...args: DateComponents): Date {
    return new Date(...args)
}

I'm not actually returning a date in my case, but this is just for testing...在我的情况下,我实际上并没有返回日期,但这仅用于测试...

The question is now how to define DateComponents .现在的问题是如何定义DateComponents I successfully implemented the 3 overloaded signatures of DateConstructor separately from each other.我成功地分别实现了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);
}

So far so good, everything works.到目前为止一切顺利,一切正常。

  • A: An empty tuple is used to cover the case where no argument is passed A:空元组用于覆盖不传参的情况
  • B: A tuple with one element covers the case where a single number, string or Date object is passed B:一个元素的元组涵盖了传递单个数字、字符串或Date对象的情况
  • C: A tuple with 2 mandatory and 5 optional number elements covers the cases where 2-7 arguments are passed C:包含 2 个必需元素和 5 个可选数字元素的元组涵盖了传递 2-7 个参数的情况

If I try to combined these 3 examples into 1, I get this:如果我尝试将这 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.
}

I get an error saying that Typescript thinks that more than 7 arguments could be passed into the Date constructor.我收到一条错误消息,说 Typescript 认为可以将 7 个以上的参数传递给Date构造函数。 I do not understand why because my type DateComponents explicitly defines tuples of 0-7 elements.我不明白为什么,因为我的类型DateComponents明确定义了 0-7 个元素的元组。

I could fix this by using some conditions and type assertions, but I'm hoping there is a prettier solution?我可以通过使用一些条件和类型断言来解决这个问题,但我希望有一个更漂亮的解决方案?

See the code in the Playground here 在此处查看 Playground 中的代码

Any idea to fix this?有什么想法可以解决这个问题吗? Thanks a lot!非常感谢!

The underlying cause is that TypeScript does not support resolving a call to an overloaded function/constructor with multiple of its call/construct signatures at once.根本原因是 TypeScript 不支持同时解析对具有多个调用/构造签名的重载函数/构造函数的调用。 The call new Date(...args);调用new Date(...args); is a single call but in order for it to be accepted, the compiler would have to break DateComponents up into its union members and see that each member is assignable to at least one construct signature.是一个单一的调用,但为了让它被接受,编译器必须将DateComponents分解成它的联合成员,并确保每个成员都可以分配给至少一个构造签名。 Instead, it sees that no single Date construct signature is applicable for the entire DateComponents union, and gives up.相反,它发现没有单个Date构造签名适用于整个DateComponents联合,并放弃。

Note that the specific wording of the error you're seeing is kind of a red herring;请注意,您看到的错误的具体措辞有点像红鲱鱼; the compiler can't accept the input and tries to shoehorn this into the available error message about number of parameters.编译器无法接受输入并尝试将其硬塞到有关参数数量的可用错误消息中。 This has happened before (eg, microsoft/TypeScript#28010 , microsoft/TypeScript#20372 ), but it hasn't seemed like a big priority to fix.这种情况以前也发生过(例如, microsoft/TypeScript#28010microsoft/TypeScript#20372 ),但似乎并不是要解决的重要问题。

Anyway, there is a (rather longstanding) open feature request in GitHub asking for overloaded functions to accept unions of parameters;无论如何,GitHub 中有一个(相当长的)开放功能请求,要求重载函数接受参数联合; see microsoft/TypeScript#14107 .请参阅microsoft/TypeScript#14107 It's not clear that this will ever happen.目前尚不清楚这是否会发生。


So, what can you do?所以,你可以做什么? The easiest thing is just to use a type assertion :最简单的事情就是使用类型断言

const d: D = function (...args: DateComponents): Date {
  return new Date(...args as ConstructorParameters<typeof Date>);
}

I know you want something "prettier", but believe me, anything I can think of to fix it turns out to be uglier.我知道你想要一些“更漂亮”的东西,但相信我,我能想到的任何解决方法都会变得更丑。


For example, you can walk the compiler through the different possibilities manually and put a bunch of redundant code in there:例如,您可以手动引导编译器通过不同的可能性,并在其中放入一堆冗余代码:

const e: D = function (...args: DateComponents): Date {
  return args.length === 0 ? new Date(...args) :
    args.length === 1 ? new Date(...args) :
      new Date(...args);
}

which is not pretty either.这也不漂亮。


Or you can try to build some code that converts an overloaded constructor into a non-overloaded one that takes a union type of parameters.或者,您可以尝试构建一些代码,将重载的构造函数转换为采用联合类型参数的非重载构造函数。 That is, manually simulate an implementation of microsoft/TypeScript#14107 .也就是说, 手动模拟 microsoft/TypeScript#14107 的实现 If you did this, the call would look like this:如果您这样做,调用将如下所示:

const f: D = function (...args: DateComponents): Date {
  return new (unifyConstructorOverloads(Date))(...args);
}

which is not that ugly itself.这本身并不是那么丑陋。 But the definition of unifyConstructorOverloads would be like this:但是unifyConstructorOverloads的定义是这样的:

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>;

which is getting homelier, uses a type assertion, and depends on a definition of ConstructorOverloads<T> , a hypothetical type function that takes an overloaded constructor and separates its multiple construct signatures into a tuple.它变得越来越简单,使用类型断言,并依赖于ConstructorOverloads<T>的定义,这是一个假设的类型函数,它采用重载的构造函数并将其多个构造签名分成一个元组。 There's no programmatic way to do this as far as I can tell, so you have to simulate that too up to some number of overloads (say, 5):据我所知,没有任何编程方法可以做到这一点,因此您必须模拟达到一定数量的重载(例如 5):

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

which, as far as aesthetics go, is well past "ugly" and probably hovering around "grotesque".就美学而言,它远远超过“丑陋”,可能徘徊在“怪诞”周围。 If you were going to do this sort of overload-unification thing in many places in your code base, I could imagine shutting away ConstructorOverloads (and an analogous Overloads for regular functions, see this question for that code) into an unlit library, so that you could use it without ever looking directly upon its abominable visage.如果你打算在你的代码库的许多地方做这种重载统一的事情,我可以想象将ConstructorOverloads (以及一个类似的常规函数​​的Overloads ,请参阅代码的这个问题)关闭到一个未点亮的库中,以便你可以使用它而无需直视它可恶的面容。


But if you're only doing it a few times, I'd strongly suggest using a type assertion and moving on.但是如果你只做几次,我强烈建议使用类型断言并继续。

Playground link to code Playground 链接到代码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM