如何键入一个包含多个元组的数组,这些元组内部具有相同的值类型,但每个元组可以不同
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
)
请记住,即使您提供了一个无效的元组,TS 也会突出显示所有这些元组,因为它将其视为一个参数。
有关元组迭代的更多信息,您可以在我的 blog
中找到
我想编写一个接收多个元组的函数,现在应该只 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
)
请记住,即使您提供了一个无效的元组,TS 也会突出显示所有这些元组,因为它将其视为一个参数。
有关元组迭代的更多信息,您可以在我的 blog
中找到