如何键入一个包含多个元组的数组,这些元组内部具有相同的值类型,但每个元组可以不同

How to type an array containing multiple tuples with the same value types inside but each tuple can be different

我想编写一个接收多个元组的函数,现在应该只 return 一个数组,这些元组将被进一步处理。 这些元组应始终包含相同类型的值。但是每个元组可以包含不同的类型,例如:

const fn = (...tuples) => [...tuples];

fn(
  [1,2], // valid
  ["a", "b"], // valid
  [() => {}, () => {}], // valid
  [1, "2"] // invalid,
  [() => 1, "foo"] // invalid
)

到目前为止我的尝试是这样的:

type TupleType<T> = [T, T];

const fn = <T>(...tuples: TupleType<T>[]) => [...tuples];

fn([1,2], ["a", "b"], [1, "2"])

https://stackblitz.com/edit/ts-all-tuple-types-same-but-different?file=index.ts

但这显然会导致使用第一个元组的类型键入函数,因此 ["a", "b"] 也是错误的。

在这种情况下,只有 [1, "2"] 应该是一个错误的参数。

提供的元组值的类型事先未知,类型可以是任何类型。

如何正确输入此函数的参数?

让我们把我们的口味分成几个更小的部分:

首先我们需要遍历每个元组并检查两个元素是否具有相同的类型。

如果两个元素共享同一类型,则此类元组应推断为string[]number[],否则应推断为(string|number)[]。所以我们需要一些实用程序来检查类型是否为联合。


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

// credits https://whosebug.com/users/125734/titian-cernicova-dragomir

type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

现在,当我们有了这样的 util 时,我们应该遍历我们的参数的推断类型,并检查每个元组是否满足我们的要求。类似于 Array.prototype.map:

type MapPredicate<T> = T extends Array<infer Elem> ? IsUnion<Elem> extends true ? never : T : never

type IsEveryValid<
  Arr extends Array<unknown>,
  Result extends Array<unknown> = []
  > = Arr extends []
  ? []
  : Arr extends [infer H]
  ? [...Result, MapPredicate<H>]
  : Arr extends [infer Head, ...infer Tail]
  ? IsEveryValid<[...Tail], [...Result, MapPredicate<Head>]>
  : Readonly<Result>;

// [number[], string[], (() => void)[], never, never]
type Result = IsEveryValid<[number[], string[], (() => void)[], (string | number)[], (string | (() => number))[]]>

如您所见,我只是将所有无效的元组替换为 never

让我们写出函数定义:

const fn = <T, Tuple extends T[]>(...tuples: [...Tuple]) => [...tuples];

等等,我们如何将我们的 util 类型应用于推断的泛型?

路口来帮忙!

const fn = <T, Tuples extends T[], IsValid extends IsEveryValid<[...Tuples]>>(...tuples: [...Tuples] & IsValid) => [...tuples];

fn(
  [1, 2], // valid
  ["a", "b"], // valid
  [() => { }, () => { }], // valid
  [1, "2"], // invalid,
  [() => 1, "foo"] // invalid
)

Playground

请记住,即使您提供了一个无效的元组,TS 也会突出显示所有这些元组,因为它将其视为一个参数。

有关元组迭代的更多信息,您可以在我的 blog

中找到