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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:46:00