TypeScript 类型定义中如何通用校验元组全元素实现属性交集筛选
需求场景
我们需要基于两个类型提取集合交集:筛选出StyleProperties中「所有元组元素都属于DOMRectReadOnly键集合」的属性键。
DOMRectReadOnly的可用键为:width/height/left/top/right/bottom- 原始的
StyleProperties结构如下,属性值均为不定长度的元组:
interface DOMRectReadOnly { readonly width: number; readonly height: number; readonly left: number; readonly top: number; readonly right: number; readonly bottom: number; // 其余属性省略 } interface StyleProperties { size: ["width", "height"]; start: ["left", "top", "x", "y"], end: ["right", "bottom"]; }
原有实现的问题
最初的类型写法只能枚举元组的前两位元素:
type DOMRectStyleProperties = { [P in keyof StyleProperties]: (StyleProperties[P][0] | StyleProperties[P][1]) extends keyof DOMRectReadOnly ? P : never }[keyof StyleProperties];
这种写法无法适配元组长度不固定、元素数量更多的场景,手动枚举所有索引的联合类型完全不可维护。
通用实现方案
通过递归遍历元组的所有元素,实现任意长度元组的全量校验,同时兼容只读元组场景。
基础工具类型
// 获取非空元组的第一个元素,空元组返回never type First<T extends readonly any[]> = T['length'] extends 0 ? never : T[0]; // 移除元组的第一个元素,返回剩余部分 type Shift<T extends readonly any[]> = T extends readonly [any, ...infer rest] ? rest : never; // 递归校验元组所有元素是否都符合指定匹配类型,全部匹配则返回原元组,否则返回never type PickTuple< Matcher extends any, Tuple extends readonly any[], Accumulation extends any[] = [] > = Tuple['length'] extends 0 // 元组遍历完成时,若累积的匹配数组为空则返回never,否则返回累积的匹配结果 ? Accumulation['length'] extends 0 ? never : Accumulation : First<Tuple> extends Matcher // 当前元素匹配时,加入累积数组后继续遍历剩余元组 ? PickTuple<Matcher, Shift<Tuple>, [...Accumulation, First<Tuple>]> // 任意一个元素不匹配,直接返回never : never;
类型适配调整
由于TS默认会把接口中定义的元组类型放宽为普通数组,且丢失字面量信息,因此需要将StyleProperties调整为常量对象,通过as const修饰保留字面量元组的完整信息:
export const StyleProperties = { size: ["width", "height"] as const, start: ["left", "top"] as const, end: ["right", "bottom"] as const };
最终类型生成逻辑
type DOMRectStyleProperties = { [P in keyof typeof StyleProperties]: // 仅当元组所有元素都属于`keyof DOMRectReadOnly`时保留当前键,否则设为never PickTuple<keyof DOMRectReadOnly, typeof StyleProperties[P]> extends never ? never : P // 索引访问过滤掉所有never项,得到最终的有效键联合类型 }[keyof typeof StyleProperties];
效果说明
上述实现完全不需要关注元组长度:
- 若元组中存在不属于
DOMRectReadOnly的元素(比如示例中start包含的x/y),递归校验会在碰到第一个不匹配元素时直接返回never,对应的start键会被自动过滤 - 最终输出的
DOMRectStyleProperties会自动匹配符合要求的键,示例场景下输出为"size" | "end"
内容的提问来源于stack exchange,提问作者Changdae Park
相关产品推荐
相关产品推荐

