如何实现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
相关产品推荐
相关产品推荐

