求实现面向元组的PickTuple与OmitTuple TypeScript工具类型
实现TypeScript元组工具类型:PickTuple与OmitTuple
问题背景
需要实现针对元组的PickTuple和OmitTuple工具类型,替代对象类型的原生Pick/Omit,要求保留元组标签,最好支持可选元组、剩余元组等复杂场景。
原实现的问题
自行编写的PickTuple返回对象类型而非元组:
export type Indices<T extends any[]> = Exclude< keyof T, keyof any[] > extends `${infer I extends number}` ? I : never; type Tuple = [unknown, ...unknown[]]; type PickTuple<T extends Tuple, K extends Indices<T>> = { [P in K]: T[P]; }; type Example = [one: number, two: string]; type Result = PickTuple<Example, 1>; // 结果:{ 1: string }
原因:使用对象字面量的映射类型{ [P in K]: T[P] }时,TypeScript会将其解析为对象而非元组。元组需要通过数组特定的映射方式生成。
实现方案
1. 先定义Indices类型(获取元组的数字索引)
该类型用于提取元组的所有数字索引,排除数组原型上的方法(如push、length等):
export type Indices<T extends any[]> = Exclude< keyof T, keyof any[] > extends `${infer I extends number}` ? I : never;
2. PickTuple实现(保留元组标签)
支持单个索引或索引数组作为参数,返回对应元素组成的元组并保留原标签:
type PickTuple<T extends any[], K extends Indices<T> | readonly Indices<T>[]> = // 处理索引数组的情况 K extends readonly Indices<T>[] ? (T extends any ? { [P in keyof K]: T[K[P]] } : never) extends infer U ? U extends any[] ? U : never : never // 处理单个索引的情况 : [T[K]];
解释:
- 当
K是索引数组时,通过映射{ [P in keyof K]: T[K[P]] }构造元组:由于K是数字索引数组,keyof K为数字类型,TypeScript会自动将映射结果解析为元组而非对象,同时保留原元组的标签。 - 当
K是单个索引时,直接返回[T[K]],TypeScript会保留该元素的元组标签。
测试示例:
type Example = [one: number, two: string, three: boolean]; type PickSingle = PickTuple<Example, 1>; // [two: string] type PickMultiple = PickTuple<Example, [0, 2]>; // [one: number, three: boolean] // 支持可选元组 type OptionalTuple = [a: number, b?: string]; type PickOptional = PickTuple<OptionalTuple, 1>; // [b?: string | undefined]
3. OmitTuple实现(保留元组标签)
基于PickTuple实现,先计算需要保留的索引,再传入PickTuple生成结果:
type OmitTuple<T extends any[], K extends Indices<T> | readonly Indices<T>[]> = K extends readonly Indices<T>[] ? PickTuple<T, Exclude<Indices<T>, K[number]>> : PickTuple<T, Exclude<Indices<T>, K>>;
解释:
- 当
K是索引数组时,用Exclude<Indices<T>, K[number]>获取所有需要保留的索引(排除掉要省略的索引)。 - 当
K是单个索引时,用Exclude<Indices<T>, K>获取保留索引。 - 最终将保留索引传入
PickTuple,得到省略指定元素后的元组,自然保留原标签。
测试示例:
type Example = [one: number, two: string, three: boolean]; type OmitSingle = OmitTuple<Example, 1>; // [one: number, three: boolean] type OmitMultiple = OmitTuple<Example, [0, 2]>; // [two: string] // 支持剩余元组 type RestTuple = [x: number, y: string, ...rest: boolean[]]; type OmitRest = OmitTuple<RestTuple, 0>; // [y: string, ...rest: boolean[]]
内容的提问来源于stack exchange,提问作者Janek Eilts
相关产品推荐
相关产品推荐

