如何正确使用TypeScript自定义类型守卫实现无断言类型收窄
实现类型守卫让EmitDTO的payload自动匹配对应类型
我尝试用自定义类型守卫但未成功,希望实现的逻辑是:拿到EmitDTO实例后,通过判断type字段,无需手动类型断言,让payload自动对应到正确的DTO类型。
现有代码如下:
enum MyEvent { Push = 'push', Forward = 'forward', } class ForwardDTO { email!: string; subscriptionID!: string; } class PushDTO { email!: string; } class EmitDTO { payload!: ForwardDTO | PushDTO; type!: MyEvent; } // 使用示例(实际会用class-transformer把JSON转成类实例,此处简化展示) const dto: EmitDTO = JSON.parse('{"payload": {"email": "my@email.com", "subscriptionID": "aabbcc"}, "type": "forward"}'); if (dto.type === MyEvent.Forward) { // 直接访问会报错,必须手动断言 // const subId = dto.payload.subscriptionID; const subId = (dto.payload as ForwardDTO).subscriptionID; // 可行,但不想依赖断言 }
我之前尝试编写类方法类型守卫,但未生效:
class EmitDTO { payload!: ForwardDTO | PushDTO; type!: MyEvent; isForward(): this.payload is ForwardDTO { return this.type === MyEvent.Forward; } }
方案一:使用区分联合类型(推荐)
TypeScript原生支持区分联合类型,只要将EmitDTO定义为包含唯一判别符的联合类型,TypeScript就能自动根据type字段推断payload的类型,无需额外类型守卫。
修改后的代码:
enum MyEvent { Push = 'push', Forward = 'forward', } class ForwardDTO { email!: string; subscriptionID!: string; } class PushDTO { email!: string; } // 将EmitDTO改为联合类型,每个成员对应一种事件的类型组合 type EmitDTO = | { type: MyEvent.Push; payload: PushDTO } | { type: MyEvent.Forward; payload: ForwardDTO }; // 使用示例 const dto: EmitDTO = JSON.parse('{"payload": {"email": "my@email.com", "subscriptionID": "aabbcc"}, "type": "forward"}'); if (dto.type === MyEvent.Forward) { // payload自动推断为ForwardDTO,可直接访问subscriptionID const subId = dto.payload.subscriptionID; } else { // payload自动推断为PushDTO const email = dto.payload.email; }
这是TypeScript官方推荐的处理该场景的方案,简洁且类型推导准确。
方案二:修正类方法的类型守卫
如果需要保留EmitDTO类的形式,需修正自定义类型守卫的写法——类方法的类型守卫不能直接断言属性类型,而是要断言整个实例的类型,让TypeScript明确:当isForward()返回true时,当前实例的type为MyEvent.Forward且payload为ForwardDTO。
修改后的代码:
enum MyEvent { Push = 'push', Forward = 'forward', } class ForwardDTO { email!: string; subscriptionID!: string; } class PushDTO { email!: string; } class EmitDTO { payload!: ForwardDTO | PushDTO; type!: MyEvent; // 类型守卫断言整个实例的类型组合 isForward(): this is EmitDTO & { type: MyEvent.Forward; payload: ForwardDTO } { return this.type === MyEvent.Forward; } isPush(): this is EmitDTO & { type: MyEvent.Push; payload: PushDTO } { return this.type === MyEvent.Push; } } // 使用示例 const dto: EmitDTO = JSON.parse('{"payload": {"email": "my@email.com", "subscriptionID": "aabbcc"}, "type": "forward"}'); if (dto.isForward()) { // payload自动推断为ForwardDTO const subId = dto.payload.subscriptionID; } else if (dto.isPush()) { // payload自动推断为PushDTO const email = dto.payload.email; }
调用isForward()或isPush()后,TypeScript就能正确推断payload的类型。
内容的提问来源于stack exchange,提问作者n1md7
相关产品推荐
相关产品推荐

