如何将递归可变元组用作函数参数类型及类型推断问题
关于递归元组类型的TypeScript类型推断问题
问题重现
你定义了如下递归元组类型:
type TupleRecursion<T> = T extends [infer HEAD, ...infer TAIL] ? [HEAD, ...TupleRecursion<TAIL>] : [];
并尝试用它约束函数参数,预期接受任意元组:
function foo<T>(tuple: TupleRecursion<T>) {}
但调用foo(["test", "hi"])时,出现错误:Source has 2 element(s) but target allows only 0。
错误原因
你的TupleRecursion<T>类型没有建立起泛型T和传入元组的有效关联。当调用函数时,TypeScript无法从TupleRecursion<T>反向推断出T应该匹配传入的元组类型,反而会默认T不满足[infer HEAD, ...infer TAIL]的分支条件,最终返回[]空元组类型。这就导致传入的2元素元组和目标类型[]不兼容,触发错误。
基础解决:让函数接受任意元组并正确推断
如果只是需要函数接受任意元组并正确推断类型,直接简化函数定义即可:
function foo<T extends any[]>(tuple: T) {} foo(["test", "hi"]); // 正常推断T为["test", "hi"]
如果要保留递归类型结构,需要调整TupleRecursion的泛型约束,让它能正向关联元组类型:
type TupleRecursion<T extends any[]> = T extends [infer HEAD, ...infer TAIL extends any[]] ? [HEAD, ...TupleRecursion<TAIL>] : []; function foo<T extends any[]>(tuple: TupleRecursion<T>) {} foo(["test", "hi"]); // 无错误,正确推断类型
进阶实现:基于前一个元素的映射约束
针对你实际需求——让元组每个元素满足基于前一个元素的映射规则,我们可以定义带前置依赖的递归类型。以下是两种常见场景的实现:
场景1:基于类型的映射约束
比如定义规则:string类型的下一个元素必须是number,number的下一个必须是boolean:
// 定义元素类型的映射规则 type MapType<T> = T extends string ? number : T extends number ? boolean : never; // 递归约束元组:第一个元素无前置限制,后续元素必须匹配前一个的映射类型 type TupleWithMapping<T, PrevType = never> = T extends [] ? [] : T extends [infer Head, ...infer Tail] ? PrevType extends never ? [Head, ...TupleWithMapping<Tail, Head>] : [MapType<PrevType>, ...TupleWithMapping<Tail, MapType<PrevType>>] : never; // 函数定义 function foo<T extends any[]>(tuple: TupleWithMapping<T>) {} // 合法调用:string → number → boolean foo(["test", 123, true]); // 错误调用:第二个元素不是number,不符合映射规则 foo(["test", "hi", true]);
场景2:基于字面量值的映射约束
如果需要针对具体字面量值做映射(比如"test"对应"testResult","hi"对应"hiResult"):
// 字面量值的映射规则 type MapLiteral<T extends string> = T extends "test" ? "testResult" : T extends "hi" ? "hiResult" : never; // 递归约束元组:每个元素必须是前一个元素的映射值 type TupleWithLiteralMapping<T> = T extends [] ? [] : T extends [infer Head extends string, ...infer Tail] ? [Head, ...TupleWithLiteralMapping<[MapLiteral<Head>, ...Tail]>] : never; function bar<T extends string[]>(tuple: TupleWithLiteralMapping<T>) {} // 合法调用 bar(["test", "testResult"]); bar(["hi", "hiResult"]); // 错误调用:第二个元素不符合"test"的映射规则 bar(["test", "hiResult"]);
内容的提问来源于stack exchange,提问作者wwohlers
相关产品推荐
相关产品推荐

