TypeScript工厂类中联合类型参数与接口不兼容的报错问题
问题代码与报错
工厂类实现:
@Injectable() export class JobApiFactoryService implements ApiServiceFactoryInterface<JobApiPayloads> { public constructor( private readonly createApplicationActivityService: CreateApplicationActivityService, private readonly createActivityService: CreateActivityService ) {} public async execute( payload: JobApiPayloads, action: ApiNamesEnum ): Promise<void> { switch (action) { case ApiNamesEnum.CREATE_JOB_APPLICATION_ACTIVITY_EXTENSION: await this.createApplicationActivityService.execute(payload); break; case ApiNamesEnum.CREATE_JOB_ACTIVITY: await this.createActivityService.execute(payload); break; default: throw new Error("Invalid action"); } } }
Payload类型定义:
export type JobApiPayloads = JobApplicationActivityExtensionPayloadInterface | JobActivityExtensionInterface;
报错信息:
Argument of type 'JobApiPayloads' is not assignable to parameter of type 'JobApplicationActivityExtensionPayloadInterface'. Type 'JobActivityExtensionInterface' is missing the following properties from type 'JobApplicationActivityExtensionPayloadInterface': jobId, candidate_ids
解决方案
方案1:类型断言(快速解决)
直接在调用服务时,将payload断言为对应方法所需的类型,让TypeScript明确当前分支的payload类型:
public async execute( payload: JobApiPayloads, action: ApiNamesEnum ): Promise<void> { switch (action) { case ApiNamesEnum.CREATE_JOB_APPLICATION_ACTIVITY_EXTENSION: await this.createApplicationActivityService.execute( payload as JobApplicationActivityExtensionPayloadInterface ); break; case ApiNamesEnum.CREATE_JOB_ACTIVITY: await this.createActivityService.execute( payload as JobActivityExtensionInterface ); break; default: throw new Error("Invalid action"); } }
说明:此方法简单直接,但依赖开发者保证action和payload的对应关系,运行时如果传错仍会出错。
方案2:类型守卫(更安全)
定义类型守卫函数,让TypeScript在分支中自动推断payload类型,避免手动断言的风险:
// 定义类型守卫函数 function isJobApplicationActivityPayload( payload: JobApiPayloads ): payload is JobApplicationActivityExtensionPayloadInterface { return 'jobId' in payload && 'candidate_ids' in payload; } function isJobActivityPayload( payload: JobApiPayloads ): payload is JobActivityExtensionInterface { // 根据JobActivityExtensionInterface的独有属性判断,示例假设它有activityId return 'activityId' in payload; } // 修改工厂类execute方法 public async execute( payload: JobApiPayloads, action: ApiNamesEnum ): Promise<void> { switch (action) { case ApiNamesEnum.CREATE_JOB_APPLICATION_ACTIVITY_EXTENSION: if (isJobApplicationActivityPayload(payload)) { await this.createApplicationActivityService.execute(payload); } else { throw new Error("Payload mismatch for action"); } break; case ApiNamesEnum.CREATE_JOB_ACTIVITY: if (isJobActivityPayload(payload)) { await this.createActivityService.execute(payload); } else { throw new Error("Payload mismatch for action"); } break; default: throw new Error("Invalid action"); } }
说明:类型守卫通过检查payload的独有属性,让TypeScript在if分支内确认payload类型,同时增加了运行时校验,比断言更安全。
方案3:关联Action与Payload类型(最严谨)
重构参数,让每个Action与对应的Payload类型强绑定,从根源避免类型不匹配:
// 定义Action与Payload的映射类型 type ApiActionMap = { [ApiNamesEnum.CREATE_JOB_APPLICATION_ACTIVITY_EXTENSION]: JobApplicationActivityExtensionPayloadInterface; [ApiNamesEnum.CREATE_JOB_ACTIVITY]: JobActivityExtensionInterface; }; // 修改execute方法的参数类型 public async execute<Action extends keyof ApiActionMap>( payload: ApiActionMap[Action], action: Action ): Promise<void> { switch (action) { case ApiNamesEnum.CREATE_JOB_APPLICATION_ACTIVITY_EXTENSION: await this.createApplicationActivityService.execute(payload); break; case ApiNamesEnum.CREATE_JOB_ACTIVITY: await this.createActivityService.execute(payload); break; default: throw new Error("Invalid action"); } }
说明:此方法通过泛型和映射类型,强制action和payload的类型对应,调用时如果传错类型,TypeScript会直接在编译阶段报错,完全避免了类型不兼容问题。
内容的提问来源于stack exchange,提问作者George Hutanu

