TypeScript条件对象属性无法生成外部可区分联合的问题
问题重现
开发者期望通过判断state.source === "Batch"来收窄state.items中元素的__typename类型,但第一种写法无法实现:
export type State<T extends "Batch" | "Shipment" = "Batch" | "Shipment"> = { source: T items: T extends "Batch" ? { __typename: "Batch" }[] : { __typename: "Shipment" }[] } const state: State = { source: "Batch", items: [] } if (state.source === "Batch") { state.items.map((i) => { const typename: "Batch" = i.__typename // 报错:类型"Batch" | "Shipment"不能赋值给类型"Batch" }) }
将State重构为泛型驱动的联合类型后,类型收窄正常工作:
export type State<T extends "Batch" | "Shipment" = "Batch" | "Shipment"> = T extends "Batch" ? { source: T items: { __typename: "Batch" }[] } : { source: T items: { __typename: "Shipment" } [] }
差异原因
第一种写法:单一对象类型 + 条件类型字段
这种写法中,State是单个对象类型,仅items字段的类型通过泛型T的条件判断计算。当你声明const state: State时,泛型T默认取值"Batch" | "Shipment",最终生成的类型是:
{ source: "Batch" | "Shipment"; items: ({ __typename: "Batch" }[] | { __typename: "Shipment" }[]); }
此时source和items的类型是独立的,TypeScript无法识别二者之间的关联逻辑——它不知道source为"Batch"时,items必须对应{ __typename: "Batch" }[],所以收窄source的类型后,items的类型不会同步收窄。
第二种写法:泛型展开的联合类型
这种写法中,State会根据泛型T的取值展开为多个对象类型的联合。当T为"Batch" | "Shipment"时,最终生成的类型是:
{ source: "Batch"; items: { __typename: "Batch" }[]; } | { source: "Shipment"; items: { __typename: "Shipment" }[]; }
这是一个标准的判别式联合类型,source字段是类型判别式。TypeScript可以识别联合类型中每个分支的结构关联,当你判断state.source === "Batch"时,会自动将整个state的类型收窄到对应分支,items的类型也随之确定为{ __typename: "Batch" }[],因此i.__typename可以安全赋值给"Batch"类型。
总结
核心差异在于类型的结构:第一种是带条件字段的单一对象,无法建立字段间的关联约束;第二种是判别式联合类型,每个分支的字段类型强绑定,TypeScript的类型收窄机制可以正常工作。
内容的提问来源于stack exchange,提问作者Athe

