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

如何正确使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 12:47:39