TypeScript中能否约束元组包含可辨识联合指定键的所有值
TypeScript 可辨识联合全值覆盖元组约束实现
可以实现该类型约束,核心通过泛型递归、字面量类型推断能力,就能完成「覆盖可辨识键所有取值、无重复、顺序任意」的元组校验,不需要引入额外依赖。
具体实现
首先从已有的Step类型中提取name字段的联合类型,再编写递归校验逻辑:
export type Step = | {name: 'a'} | {name: 'b'} | {name: 'c', foo: string} | {name: 'd'}; // 提取name字段的所有字面量联合:'a' | 'b' | 'c' | 'd' type StepName = Step['name']; // ------------------------------ // 类型校验工具逻辑 // ------------------------------ // 辅助:联合转交叉类型,用于取联合最后一个成员 type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; // 辅助:取联合类型的最后一个成员 type LastInUnion<U> = UnionToIntersection<U extends unknown ? (x: U) => void : never> extends (x: infer L) => void ? L : never; // 辅助:联合转元组,用于统计联合成员总个数 type UnionToTuple<U, Last = LastInUnion<U>> = [U] extends [never] ? [] : [...UnionToTuple<Exclude<U, Last>>, Last]; // 所有name值的总个数,用于快速校验元组长度 type STEP_NAME_COUNT = UnionToTuple<StepName>['length']; // 核心递归:逐元素排除已出现的name,最终剩余联合为never则校验通过 type ExcludeUsed<T extends readonly unknown[], U> = T extends readonly [infer First, ...infer Rest] ? First extends U ? ExcludeUsed<Rest, Exclude<U, First>> : 1 // 出现不在联合内的非法值,返回非never标记校验失败 : U; type ValidateStepNames<T extends readonly StepName[]> = T['length'] extends STEP_NAME_COUNT ? ExcludeUsed<T, StepName> extends never ? T : never : never; // 辅助构造函数,自动推断传入元组的类型做校验,无需手动声明泛型 const defineStepNames = <T extends readonly StepName[]>(arr: ValidateStepNames<T>) => arr;
校验效果
传入数组时必须加as const断言,让TypeScript保留精确的字面量类型、元组长度信息,校验规则完全匹配需求:
// ✅ 校验通过:覆盖所有name值、无重复、顺序任意 const stepNames1 = defineStepNames(['d', 'a', 'b', 'c'] as const); // ❌ 校验报错:缺失'c' const stepNames2 = defineStepNames(['d', 'a', 'b'] as const); // ❌ 校验报错:'c'重复 const stepNames3 = defineStepNames(['d', 'a', 'b', 'c', 'c'] as const); // ❌ 校验报错:存在不在联合内的非法值'e' const stepNames4 = defineStepNames(['d', 'a', 'b', 'e'] as const);
实现原理
- 先通过元组长度快速过滤明显不符合要求的输入:长度不等于name值总个数的数组,必然存在缺项或重复问题
- 递归遍历元组的每一项,每遇到一个合法的name值就从待匹配集合中排除该值,遍历完成后如果所有值都被匹配且无额外值,则校验通过
- 只要出现缺项、重复项、非法值任意一种情况,类型就会不匹配,TypeScript会直接抛出类型错误
注意:如果不加
as const断言,TypeScript会把数组类型拓宽为StepName[],丢失单个元素的字面量信息和元组长度信息,无法完成精确校验。
内容的提问来源于stack exchange,提问作者Dmitry Minkovsky
相关产品推荐
相关产品推荐

