数组访问后类型收窄时,TypeScript区分联合类型失效问题
解决联合类型数组元素的类型收窄问题
先看你的类型定义和问题场景:
定义基础类型:
type Prospect = { name: string; } type Client = { name: string; account: string; }
创建带区分符的联合类型:
type Union = | {kind: "prospect", persons: Prospect[]} | {kind: "client", persons: Client[]}; const {persons, kind} = {} as Union
直接在kind判断块里遍历persons时,类型收窄正常:
if (kind === "client") { persons.map(person => { // person: Client person.account // 无问题 }) }
但提前取出数组元素后,kind判断无法收窄元素类型:
const person = persons[0] if (kind === "client") { // person: Prospect | Client person.account // 报错 }
原因是TypeScript无法跟踪提前取出的person变量和kind之间的关联关系——取出person时persons的类型还是Prospect[] | Client[],后续判断kind时,TS不会自动回溯修正person的类型。
可以用以下几种方法解决,不用给Prospect和Client添加冗余的kind字段:
方法一:在类型收窄后再取出元素
这是最简单直接的方案,把取数组元素的操作放到kind判断的代码块内部,此时TS已经把persons的类型收窄为Client[],取出的元素自然是Client类型:
const { persons, kind } = {} as Union; if (kind === "client") { const person = persons[0]; // person: Client person.account; // 正常访问,无报错 }
方法二:使用类型断言(谨慎使用)
如果业务逻辑能保证当前kind对应的person类型正确,可以用类型断言强制指定类型,不过这种方法需要自己确保逻辑严谨,否则容易出现运行时错误:
const person = persons[0]; if (kind === "client") { (person as Client).account; // 正常访问 }
方法三:自定义类型守卫函数
创建一个关联kind和元素类型的类型守卫,让TS能通过kind判断来收窄person的类型:
function isClientPerson(kind: Union["kind"], person: Prospect | Client): person is Client { return kind === "client"; } const { persons, kind } = {} as Union; const person = persons[0]; if (isClientPerson(kind, person)) { person.account; // 类型自动收窄为Client,无报错 }
方法四:重构联合类型,用泛型绑定关联关系
重新定义联合类型,通过泛型把kind和对应的数组元素类型做绑定,让TS能更精准地跟踪类型关联:
// 先建立kind到元素类型的映射 type PersonTypeMap = { prospect: Prospect; client: Client; }; // 用泛型定义Union,确保kind和persons类型严格绑定 type Union<K extends keyof PersonTypeMap = keyof PersonTypeMap> = { kind: K; persons: PersonTypeMap[K][]; }; const { persons, kind } = {} as Union; // 辅助函数获取元素,自动推导正确类型 function getPerson<K extends keyof PersonTypeMap>(union: Union<K>, index: number): PersonTypeMap[K] { return union.persons[index]; } const person = getPerson({ persons, kind }, 0); if (kind === "client") { person.account; // 类型正确,无报错 }
内容的提问来源于stack exchange,提问作者Kratt
相关产品推荐
相关产品推荐

