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

TypeScript条件类型结合联合类型映射的异常行为及解决问询

联合类型映射+条件类型的异常行为问题

先定义联合类型Entities:

export type CompErrors = {
    type: "compilation_errors"
    val: number
}

export type TestErrors = {
    type: "test_errors"
}

export type TestInfo = {
    type: 'test_info'
}

export type Entities = CompErrors | TestErrors | TestInfo

创建EntityMap类型将联合转为以type为键的对象类型,此时函数参数要求必填所有键:

type EntityMap<Type> = {
    [T in Extract<Type, { type: string }> as T["type"]]: ((par: T) => any) | string | boolean | object
}

type Params = EntityMap<Entities>

const entityMapFn = (params: Params) => {}

// 缺少test_info会报错
entityMapFn({
    compilation_errors: "",
    test_errors: "",
})

但添加布尔条件分支后,即便分支未触发,生成的类型不再要求必填所有属性:

type EntityMapAlternative<Type> = Type extends boolean ? string : {
    [T in Extract<Type, { type: string }> as T["type"]]: ((par: T) => any) | string | boolean | object
}

type ParamsAlternative = EntityMapAlternative<Entities>

const entityMapAlternativeFn = (params: ParamsAlternative) => {}

// 缺少test_info无报错
entityMapAlternativeFn({
    compilation_errors: "",
    test_errors: (te) => {te.type},
})

提取映射类型后结果仍一致:

type EntityMapExtracted<Type> = Type extends boolean ? string : EntityMap<Type>

type ParamsExtracted = EntityMapExtracted<Entities>

const entityMapExtractedFn = (params: ParamsExtracted) => {}

// 缺少test_info无报错
entityMapExtractedFn({
    compilation_errors: "",
    test_errors: (te) => {te.type},
})

疑问:为何布尔分支未触发却改变了类型结果?如何实现预期的必填所有属性的效果?


原因分析与解决方案

原因:条件类型的分布式特性

当条件类型的泛型参数是联合类型时,TypeScript会自动触发分布式条件类型——它会把联合类型拆成单个成员,分别代入条件类型计算,最后把结果重新合并成联合类型。

在这个例子里,Entities是CompErrors | TestErrors | TestInfo,代入Type extends boolean ? ... : ...时,会拆成三次独立计算:

  1. CompErrors extends boolean ? string : EntityMap<CompErrors> → 得到{ compilation_errors: ... }
  2. TestErrors extends boolean ? string : EntityMap<TestErrors> → 得到{ test_errors: ... }
  3. TestInfo extends boolean ? string : EntityMap<TestInfo> → 得到{ test_info: ... }

最终ParamsAlternative是这三个单键对象类型的联合:{ compilation_errors: ... } | { test_errors: ... } | { test_info: ... }。而TypeScript中,联合类型的对象只需要满足其中一个成员的结构即可,所以缺少test_info不会报错——因为传入的对象符合第一个联合成员的结构。

而最初的EntityMap<Entities>没有条件类型,直接对整个联合类型做映射,生成的是包含所有三个键的必填对象类型,所以缺少键会报错。

解决方案:禁用分布式条件类型

要避免分布式特性,只需要把泛型参数用元组包裹,让TypeScript把联合类型当成一个整体处理:

方案1:直接修改条件类型

type EntityMapAlternative<Type> = [Type] extends [boolean] ? string : {
    [T in Extract<Type, { type: string }> as T["type"]]: ((par: T) => any) | string | boolean | object
}

type ParamsAlternative = EntityMapAlternative<Entities>
// 此时ParamsAlternative是包含三个必填键的对象类型,缺少test_info会报错

方案2:复用已有EntityMap类型

type EntityMapExtracted<Type> = [Type] extends [boolean] ? string : EntityMap<Type>

type ParamsExtracted = EntityMapExtracted<Entities>
// 同样生成包含所有必填键的对象类型

原理是:[Type]是一个元组类型,即使Type是联合类型,[Type]也会被视为单个整体类型,不会触发分布式拆分。此时条件判断会针对整个联合类型做检查,而不是每个成员,因此会进入分支EntityMap<Type>,生成预期的全必填键对象类型。

内容的提问来源于stack exchange,提问作者Jarek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 03:52:23