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

TypeScript编写泛型zipN函数遇错误:Mapped type非数组类型

编写TypeScript泛型zipN函数遇到的类型错误

我在实现TypeScript泛型zipN函数时碰到了问题,代码最后一行报错:Type 'ZipN<unknown[][]>' is not an array type。

为了避免ZipN生成的对象包含原型方法这类非索引属性,我定义了OnlyNumeric类型并作用于keyof LongestN<T>,但结果ZipN生成的是带数字键的普通对象,不是预期的数组/元组类型。但奇怪的是,{ [L in keyof T]: MaybeIndex<T[L], K> }明明能生成数组类型,我搞不懂问题出在哪。

代码如下:

type Keys<T> = { [K in keyof T]: K };
type Longest2<T extends unknown[], U extends unknown[]> = Keys<T> extends [...Keys<U>, ...infer _] ? T : U;
type LongestN<T extends unknown[][]> = T extends [infer T0 extends unknown[], ...infer Ts extends unknown[][]] ? Longest2<T0, LongestN<Ts>> : T[0];
type OnlyNumeric<T> = T extends `${number}` ? T : never;
type MaybeIndex<T, K> = K extends keyof T ? T[K] : never;
type ZipN<T extends unknown[][]> = { [K in OnlyNumeric<keyof LongestN<T>>]: { [L in keyof T]: MaybeIndex<T[L], K> } };

export const zipN = <T extends unknown[][]>(...[head, ...tail]: T): ZipN<T> => [head, ...zipN(...tail) as ZipN<typeof tail>] as any;

问题根源

TypeScript里,只有当索引签名是number类型,并且类型符合数组的结构特征时,才会被识别为数组/元组类型。你用OnlyNumeric<keyof LongestN<T>>作为索引,这里的OnlyNumeric返回的是字符串类型的数字(比如"0"、"1"),而非number类型。所以ZipN最终生成的是带有字符串数字键的对象,不是数组。

另外,keyof LongestN<T>对于数组来说会包含所有数字索引的字符串形式("0"、"1"...)以及数组原型上的属性名(比如"length"、"push"),你用OnlyNumeric过滤后剩下的是字符串数字键,但这依然是字符串类型,不符合数组要求的number索引规则。


修复方案

要让ZipN生成数组/元组类型,需要把索引从字符串数字转为number类型,同时利用TypeScript的元组推导特性:

步骤1:重构ZipN类型

通过生成指定长度的元组来推导数字索引,再基于这些索引映射出最终的数组类型:

// 生成长度为N的元组,用于推导数字索引
type TupleOfLength<N extends number, T = unknown, R extends T[] = []> = R['length'] extends N ? R : TupleOfLength<N, T, [...R, T]>;

// 获取最长数组的长度
type LongestLength<T extends unknown[][]> = LongestN<T>['length'];

// 重构ZipN:基于最长长度的元组索引进行映射
type ZipN<T extends unknown[][]> = {
  [K in keyof TupleOfLength<LongestLength<T>>]: {
    [L in keyof T]: MaybeIndex<T[L], K & number>
  }
};

步骤2:修正LongestN的边界情况

原LongestN在处理空数组时返回T[0],会得到undefined,需要调整为返回空数组:

type LongestN<T extends unknown[][]> = T extends [infer T0 extends unknown[], ...infer Ts extends unknown[][]] 
  ? Longest2<T0, LongestN<Ts>> 
  : [];

完整修复后的代码

type Keys<T> = { [K in keyof T]: K };
type Longest2<T extends unknown[], U extends unknown[]> = Keys<T> extends [...Keys<U>, ...infer _] ? T : U;
type LongestN<T extends unknown[][]> = T extends [infer T0 extends unknown[], ...infer Ts extends unknown[][]] ? Longest2<T0, LongestN<Ts>> : [];
type MaybeIndex<T, K> = K extends keyof T ? T[K] : never;

// 生成指定长度的元组
type TupleOfLength<N extends number, T = unknown, R extends T[] = []> = R['length'] extends N ? R : TupleOfLength<N, T, [...R, T]>;
type LongestLength<T extends unknown[][]> = LongestN<T>['length'];

// 重构后的ZipN类型
type ZipN<T extends unknown[][]> = {
  [K in keyof TupleOfLength<LongestLength<T>>]: {
    [L in keyof T]: MaybeIndex<T[L], K & number>
  }
};

export const zipN = <T extends unknown[][]>(...[head, ...tail]: T): ZipN<T> => {
  if (tail.length === 0) return head.map(item => [item]) as ZipN<T>;
  const zippedTail = zipN(...tail);
  return head.map((item, idx) => [item, ...(zippedTail[idx] || [])]) as ZipN<T>;
};

修复效果

现在ZipN会生成正确的数组/元组类型,比如调用zipN([1,2], ['a','b','c'])时,返回类型是[[1, 'a'], [2, 'b'], [undefined, 'c']],符合预期,也不会再出现数组类型错误。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 09:44:54