You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将递归可变元组用作函数参数类型及类型推断问题

关于递归元组类型的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 07:23:13