如何在TypeScript中传递泛型类型作为类型参数实现通用元组组合?
TypeScript泛型元组合并工具的泛化实现问题
问题背景
我编写了一个用于合并两个TypeScript元组的工具类型TupleIntersect,实现逻辑如下:
type TupleIntersect<L extends unknown[], R extends unknown[]> = L extends [infer LH, ...infer LT] ? R extends [infer RH, ...infer RT] ? [LH & RH, ...TupleIntersect<LT, RT>] // 左右都是非空元组 : [LH & R[number], ...TupleIntersect<LT, R>] // 左非空,右任意 : R extends [infer RH, ...infer RT] ? [RH & L[number], ...TupleIntersect<L, RT>] // 右非空,左任意 : L extends [] ? R extends [] ? [] // 左右都是空元组 : R[number] // 左空,右非空 : R extends [] ? L[number] // 右空,左非空 : (L[number] & R[number])[]; // 左右都是数组类型
为了避免为元组联合等场景重复编写类似逻辑,我希望将其泛化,设计了TupleZip工具类型,通过传入不同的组合器实现不同的合并逻辑:
type TupleZip<L extends unknown[], R extends unknown[], Combinator, Default> = L extends [infer LH, ...infer LT] ? R extends [infer RH, ...infer RT] ? [Combinator<LH, RH>, ...TupleZip<LT, RT, Combinator, Default>] : [Combinator<LH, R[number]>, ...TupleZip<LT, R, Combinator, Default>] : R extends [infer RH, ...infer RT] ? [Combinator<L[number], R>, ...TupleZip<L, RT, Combinator, Default>] : L extends [] ? R extends [] ? [] : [Combinator<Default, R[number]>] : R extends [] ? [Combinator<L[number], Default>] : Combinator<L[number], R[number]>[];
预期的使用方式如下:
type Intersect<L, R> = L & R; type TupleIntersect<L extends unknown[], R extends unknown[]> = TupleZip<L, R, Intersect, unknown>; type Union<L, R> = L | R; type TupleUnion<L extends unknown[], R extends unknown[]> = TupleZip<L, R, Union, never>;
但TypeScript编译器报错**“类型参数不是泛型类型”**,请问是否有合法实现方式?是否过度类比了C++的模板模板参数?
解决方案
TypeScript目前不支持C++风格的模板模板参数(即直接传递泛型类型作为另一个泛型的参数),但可以通过泛型函数类型模拟类型构造器的方式实现需求:
1. 定义组合器类型
将组合器设计为泛型函数类型,用于描述两个类型的组合逻辑:
// 交集组合器:返回两个类型的交集 type IntersectCombiner = <T, U>(t: T, u: U) => T & U; // 并集组合器:返回两个类型的并集 type UnionCombiner = <T, U>(t: T, u: U) => T | U;
2. 实现通用的TupleZip
通过条件类型推断组合器的返回类型,完成元组的遍历与组合:
// 提取组合器的返回类型 type Combine<T, U, Combiner> = Combiner extends <A, B>(a: A, b: B) => infer Res ? Res : never; // 通用元组组合工具类型 type TupleZip< L extends unknown[], R extends unknown[], Combiner, Default > = L extends [infer LH, ...infer LT] ? R extends [infer RH, ...infer RT] ? [Combine<LH, RH, Combiner>, ...TupleZip<LT, RT, Combiner, Default>] : [Combine<LH, R[number], Combiner>, ...TupleZip<LT, R, Combiner, Default>] : R extends [infer RH, ...infer RT] ? [Combine<L[number], RH, Combiner>, ...TupleZip<L, RT, Combiner, Default>] : L extends [] ? R extends [] ? [] : [Combine<Default, R[number], Combiner>] : R extends [] ? [Combine<L[number], Default, Combiner>] : Combine<L[number], R[number], Combiner>[];
3. 使用示例
通过传入不同的组合器,快速实现元组交集、并集等逻辑:
// 元组交集 type TupleIntersect<L extends unknown[], R extends unknown[]> = TupleZip<L, R, IntersectCombiner, unknown>; // 元组并集 type TupleUnion<L extends unknown[], R extends unknown[]> = TupleZip<L, R, UnionCombiner, never>;
原理说明
TypeScript不允许直接传递泛型类型别名作为参数,但可以通过泛型函数类型模拟“类型构造器”,再通过ReturnType和条件类型推断提取组合后的类型,以此绕过模板模板参数的限制。
内容的提问来源于stack exchange,提问作者Simon Struthers
相关产品推荐
相关产品推荐

