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

如何实现TypeScript zip函数的元组/数组智能类型推断?

实现支持元组与数组自动推断的zip函数类型

明确结论:这个需求完全可以实现。我们需要先构建几个辅助类型,再完成ZippedTupleOrArray的定义,最后修改zip函数的类型断言即可。

步骤1:构建辅助类型

1.1 判断类型是否为元组

元组的length是字面量类型,普通数组的length是number类型,据此写出判断逻辑:

type IsTuple<T> = T extends readonly any[] 
  ? number extends T['length'] 
    ? false 
    : true 
  : false;

1.2 判断所有输入是否均为元组

遍历输入参数的类型,检查每一项是否都是元组:

type AllTuples<T extends unknown[][]> = { 
  [K in keyof T]: IsTuple<T[K]> 
} extends { 
  [K in keyof T]: true 
} ? true : false;

1.3 获取元组的长度字面量

直接提取元组的length属性:

type TupleLength<T extends readonly any[]> = T['length'] extends number ? T['length'] : never;

1.4 计算多个元组的最小长度

先实现两个数字的最小值比较,再递归遍历所有元组取最小:

type Min<A extends number, B extends number> = 
  `${A}` extends `${infer ANum extends number}` 
    ? `${B}` extends `${infer BNum extends number}` 
      ? ANum < BNum ? A : B 
      : never 
    : never;

type MinTupleLength<T extends unknown[][]> = 
  T extends [infer First extends readonly any[], ...infer Rest extends unknown[][]] 
    ? Min<TupleLength<First>, MinTupleLength<Rest>> 
    : 0; // 空输入时默认长度为0

步骤2:实现ZippedTupleOrArray类型

结合上述辅助类型,判断所有输入是否为元组,分别返回对应类型:

type ZippedTupleOrArray<T extends unknown[][]> = 
  AllTuples<T> extends true 
    ? StaticArray<MinTupleLength<T>, ZippedElement<T>> 
    : Array<ZippedElement<T>>;

步骤3:修改zip函数的类型断言

将原函数的返回类型断言替换为ZippedTupleOrArray<T>,完整代码如下:

type ZippedElement<T extends unknown[][]> = {
  [K in keyof T]: T[K] extends (infer V)[] ? V : never;
};

type StaticArray<L extends number, T, R extends any[] = T[]> =
  R extends { length: L }
    ? R
    : StaticArray<L, T, [...R, T]>;

// 辅助类型
type IsTuple<T> = T extends readonly any[] ? number extends T['length'] ? false : true : false;
type AllTuples<T extends unknown[][]> = { [K in keyof T]: IsTuple<T[K]> } extends { [K in keyof T]: true } ? true : false;
type TupleLength<T extends readonly any[]> = T['length'] extends number ? T['length'] : never;
type Min<A extends number, B extends number> = `${A}` extends `${infer ANum extends number}` ? `${B}` extends `${infer BNum extends number}` ? ANum < BNum ? A : B : never : never;
type MinTupleLength<T extends unknown[][]> = T extends [infer First extends readonly any[], ...infer Rest extends unknown[][]] ? Min<TupleLength<First>, MinTupleLength<Rest>> : 0;

type ZippedTupleOrArray<T extends unknown[][]> = AllTuples<T> extends true ? StaticArray<MinTupleLength<T>, ZippedElement<T>> : Array<ZippedElement<T>>;

export function zip<T extends unknown[][]>(...args: T) {
  const minLength = Math.min(...args.map((arr) => arr.length));

  return Array.from({ length: minLength }, (_, i) =>
    args.map((arr) => arr[i]),
  ) as ZippedTupleOrArray<T>;
}

测试验证

情况1:输入均为元组

const a: [number, number] = [1, 2];
const b: [string, string] = ["a", "b"];
const result1 = zip(a, b); // 类型推断为 [[number, string], [number, string]]

情况2:输入包含普通数组

const c: number[] = [1, 2, 3];
const d: string[] = ["a", "b"];
const result2 = zip(c, d); // 类型推断为 [number, string][]

情况3:混合元组与数组

const e: [number, number] = [1, 2];
const f: string[] = ["a", "b", "c"];
const result3 = zip(e, f); // 类型推断为 [number, string][]

内容的提问来源于stack exchange,提问作者Guillaume Racicot

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 09:35:12