我最近遇到了一个 API,其中的数组由任意数量的重复元组构成。我尝试了一些替代方法来实现它,但遇到了具有无限递归的类型或只是不表达约束的问题。
我如何能够按照下面的情况输入 RepeatedTuple?
//Possibly a recursive conditional way to define RepeatedTuple?
// https://github.com/microsoft/TypeScript/pull/40002
type Example = RepeatedTuple<[string,number]>;
const example0:Example = [];
const example1:Example = ["hello",1];
const example2:Example = ["hello",1,"hello",2];
const example3:Example = ["hello",1,"hello",2,"hello",3];
const example4:Example = ["hello",1,"hello",2,"hello",3,"hello",4];
在 https://www.typescriptlang.org/play?ts=4.2.0-beta&ssl=1&ssc=1&pln=10&pc=68#code/PTAKHsGdISwIwDYE8AEBDFAnApgYwK6awBu2Ku4AdgCYwAuMVaCKA7mqneCtdgGYxKZAErYADtjR1s1ACr4xCbAH4AUCBQALOnTGQAXCADm9TfjgA6CgFtg1mLkxRwfOsFlIJAZUcwxbsXwEBGAAFgAGSIAmVVU6TzIAUQAPNGtFMgBeFFEJKRl5DIAeAG1IOkxBIwAaSnxrOGxMAF0APgBuWIpKcpRsVPSlcP0UtIyUbJLmzu7e-rGlAEYRgfHJgCJNbGDwderF6dVZuj7VpSiVhayUEs3thF396rudvajD49OrgGZLwevbltXk8Xg83s8gWDqt8PlQ5mdsKE-msbqDHosIfdHlFMcDvrioaFpkA 有一个 Typescript 游乐场有上述情况。
最佳答案
TypeScript 中没有任何特定类型表示“由 string, number
的任意数量的副本组成的 tuple”。在这种情况下,你能得到的最接近的是一些有限的 union涵盖您实际预期的用例,或一些自我引用 generic可用于验证候选元组的约束。
请注意,虽然后者似乎允许完全任意长度的元组,但编译器对条件类型的递归限制相当浅(深度约为 25 左右)。因为有限联合与元组的长度成线性比例,所以实际上与递归相比,它会走得更远。
这是一种写法 RepeatedTuple
对于长达 40 次重复的长度:
type _R<T extends readonly any[], U extends readonly any[]> =
readonly [] | readonly [...U, ...T];
type RepeatedTuple<T extends readonly any[]> =
_R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T,
_R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T,
_R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T,
_R<T, _R<T, _R<T, _R<T, readonly []>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>;
这使用 variadic tuples连接数组,我们只需手动组合一个联合或连接操作 40 次。如果需要,您可以添加到其中,尽管最终这会达到一定的限制。让我们看看它是如何工作的:type Example = RepeatedTuple<[string, number]>
const example0: Example = [];
const example1: Example = ["hello", 1];
const example2: Example = ["hello", 1, "hello", 2];
const example3: Example = ["hello", 1, "hello", 2, "hello", 3];
const example4: Example = ["hello", 1, "hello", 2, "hello", 3, "hello", 4];
const badExample0: Example = ["hello"] // error!
// Source has 1 element(s) but target requires 80.
const badExample1: Example = ["hello", "goodbye"]; // error!
// Type 'string' is not assignable to type 'number | undefined'.
const badExample2: Example = ["hello", 1, "hello"]; // error!
// Source has 3 element(s) but target requires 80.
const badExample3: Example = ["hello", 1, "hello", 2, false]; // error!
// Type 'false' is not assignable to type 'string | undefined'.
const badExample4: Example = ["hello", 1, "hello", 2, "hello"]; // error!
// Source has 5 element(s) but target requires 80.
const limits: Example = ["", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0,
"", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0,
"", 0, "", 0, "", 0, "", 0, "", 0, "", 0]; // okay
这看起来不错。连比较长的limits
示例工作。失败案例的一些错误消息有点奇怪,因为编译器在解释你犯错的原因时侧重于长度为 80 的元组。但它按预期工作。但是,如果您创建了一个长度为 82 或更大的元组,这将失败。为了完整起见,让我们探索通用约束方法:
type ValidRepeatedTuple<T extends readonly any[], U extends readonly any[]> =
U extends readonly [] ? U :
readonly [...T, ...(U extends readonly [...T, ...infer R] ? ValidRepeatedTuple<T, R> : [])];
const asValidRepeatedTuple = <T extends readonly any[]>() =>
<U extends readonly any[]>(u: ValidRepeatedTuple<T, U>) => u;
这是使用 recursive conditional type检查候选元组 U
反对元组到重复的串联 T
.如 U
是 T
的有效重复,然后 U
将延长 ValidRepeatedTuple<T, U>
.否则,ValidRepeatedTuple<T, U>
将一个“附近”有效元组,以便错误消息将给出关于如何修复错误的更合理的提示。请注意,添加泛型意味着需要指定泛型类型参数。为了让开发人员不必完全手动执行此操作,我添加了帮助函数
asValidRepeatedTuple()
以便 U
可以推断。此外,由于您想手动指定 T
,我们需要制作 asValidRepeatedTuple()
curried函数,因为目前无法进行 microsoft/TypeScript#26242 中请求的排序的部分类型参数推断;在具有类型参数的单个泛型函数调用中 T
和 U
,您要么需要手动指定两者,要么让编译器推断两者。柯里化允许您手动指定 T
同时让编译器推断 U
, 代价是在那里有一个额外的函数调用。所以,这有点复杂……它有效吗?让我们来看看:
const asExample = asValidRepeatedTuple<[string, number]>();
const example0 = asExample([]);
const example1 = asExample(["hello", 1]);
const example2 = asExample(["hello", 1, "hello", 2]);
const example3 = asExample(["hello", 1, "hello", 2, "hello", 3]);
const example4 = asExample(["hello", 1, "hello", 2, "hello", 3, "hello", 4]);
const badExample0 = asExample(["hello"]); // error!
// Source has 1 element(s) but target requires 2.
const badExample1 = asExample(["hello", "goodbye"]); // error!
// Type 'string' is not assignable to type 'number'.
const badExample2 = asExample(["hello", 1, "hello"]); // error!
// Source has 3 element(s) but target requires 4.
const badExample3 = asExample(["hello", 1, "hello", 2, false]); // error!
// Type 'boolean' is not assignable to type 'string'.
const badExample4 = asExample(["hello", 1, "hello", 2, "hello"]); // error!
// Source has 5 element(s) but target requires 6.
是的,看起来不错。错误消息比以前的版本更好,因为它们提到了一个比编译器从有限联合中选择的更“附近”的元组。 (与其说“你的长度 3
元组不好,你应该让它成为 80
”,而是说“你应该让它成为 4
。)但这里最大的缺点是递归限制:
const limits = asExample(["", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0,
"", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0,
"", 0, "", 0, "", 0, "", 0, "", 0, "", 0]); // error!
// Type instantiation is excessively deep and possibly infinite.(2589)
长度为 46 的元组太长,因为编译器递归太多。有限联合看起来更有限,但实际上更擅长处理更长的元组。您可以通过手动展开一些递归来解决这个问题,从而将限制扩大到更大一些。但它不太可能达到手动联合。您可能能够编写一个不太直接的递归通用约束,通过在递归的每一步将元组加倍或不加倍,该约束随元组的长度成对数缩放。看 this GitHub issue comment对于类似的技术。这可能适用于很长的元组,但有人很难理解它在做什么(甚至比这里已经存在的东西更难)而且我还没有让它工作......所以我放弃了。
最后,即使您完善了这一点,也要注意它只能真正用于验证候选特定元组。编译器将无法遵循通用元组的逻辑。因此,例如,在接受
ValidRepeatedTuple<T, U>
的函数中对于一些通用的 U
,编译器将无可救药地迷失于这意味着什么:function hmm<U extends readonly any[]>(t: ValidRepeatedTuple<[string, number], U>) {
t[0].toUpperCase() // compiler thinks it's a string, but hey, it might be undefined
t[1].toFixed() // compiler thinks it's a number, but hey, it might be undefined
t[2]?.toUpperCase() // error! compiler thinks it's undefined, but hey, it might be a string
}
对于 big-old-union 案例,情况稍微好一些:function hmm(t: Example) {
t[0]?.toUpperCase() // okay
t[1]?.toFixed() // okay
t[2]?.toUpperCase() // okay
for (let i = 0; i < t.length / 2; i++) {
t[2 * i]?.toUpperCase(); // error
t[2 * i + 1]?.toFixed(); // error
}
}
虽然你可以看到它并不真正理解你可能正在做的操作类型,所以🤷♂️。Playground link to code
关于arrays - 可以在 Typescript 中定义重复的元组吗,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66298024/