TypeScript类中实现二选一属性必填的解决方案问询
问题描述
注意:我知道这个问题和一个现有线程极为相似,但它不适用于Class场景,而且类无法扩展接口,这让我陷入了困境。
我在自学过程中遇到了一个问题:想给类添加类型约束,要求交易相关的类实例必须包含两个属性中的一个——具体来说,实例必须存在bankAccountNumber或encryptedBankAccountNumber属性。
我的AchDetails类代码如下:
export class AchDetails { 'bankAccountNumber'?: string; 'encryptedBankAccountNumber'?: string; 'type'?: AchDetails.TypeEnum; static discriminator: string | undefined = undefined; static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ { "name": "bankAccountNumber", "baseName": "bankAccountNumber", "type": "string" }, { "name": "encryptedBankAccountNumber", "baseName": "encryptedBankAccountNumber", "type": "string" } ]; static getAttributeTypeMap() { return AchDetails.attributeTypeMap; } } export namespace AchDetails { export enum TypeEnum { Ach = 'ach', AchPlaid = 'ach_plaid' } }
我原本想用类似的接口方案,但该方案对类不适用(据我理解,类无法直接使用外部类型),相关代码如下:
interface AchBaseDetails { 'bankAccountNumber'?: string; 'encryptedBankAccountNumber'?:string; } type RequireBankNumberType<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & { [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys] export type RequiredAchDetails = RequireBankNumberType<AchBaseDetails, 'bankAccountNumber' | 'encryptedBankAccountNumber'>
请问有没有办法实现该需求,使其适用于当前的Class实例?
解决方案
你可以通过类型约束+运行时校验的组合方式实现需求,以下是几种可行方案:
1. 编译时类型约束(无需修改原类)
直接复用你定义的RequireBankNumberType工具类型,将AchDetails类作为泛型参数传入,在创建或使用实例时用该类型做约束即可:
// 直接将AchDetails类作为泛型参数传入类型工具 type RequiredAchDetails = RequireBankNumberType<AchDetails, 'bankAccountNumber' | 'encryptedBankAccountNumber'>; // 合法实例 const validAch1: RequiredAchDetails = { bankAccountNumber: '123456', type: AchDetails.TypeEnum.Ach }; const validAch2: RequiredAchDetails = { encryptedBankAccountNumber: 'encrypted-123', type: AchDetails.TypeEnum.AchPlaid }; // 编译报错:缺少必需属性 const invalidAch: RequiredAchDetails = { type: AchDetails.TypeEnum.Ach };
这种方式仅在编译阶段校验,适合静态创建的实例,无法处理动态生成的实例(比如后端返回数据)。
2. 构造函数添加运行时校验(编译+运行双重保障)
如果要确保所有实例都符合约束,可以改造类的构造函数,同时保留编译时类型检查:
export class AchDetails { 'bankAccountNumber'?: string; 'encryptedBankAccountNumber'?: string; 'type'?: AchDetails.TypeEnum; static discriminator: string | undefined = undefined; static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ { "name": "bankAccountNumber", "baseName": "bankAccountNumber", "type": "string" }, { "name": "encryptedBankAccountNumber", "baseName": "encryptedBankAccountNumber", "type": "string" } ]; static getAttributeTypeMap() { return AchDetails.attributeTypeMap; } // 用类型约束构造函数参数,同时添加运行时校验 constructor(data: RequireBankNumberType<AchDetails, 'bankAccountNumber' | 'encryptedBankAccountNumber'>) { Object.assign(this, data); // 防止绕过类型约束的非法实例 if (!this.bankAccountNumber && !this.encryptedBankAccountNumber) { throw new Error("AchDetails实例必须包含bankAccountNumber或encryptedBankAccountNumber其中一个属性"); } } } // 合法创建实例 const validAch = new AchDetails({ bankAccountNumber: '123456', type: AchDetails.TypeEnum.Ach }); // 编译报错,运行时也会抛出错误 const invalidAch = new AchDetails({ type: AchDetails.TypeEnum.Ach });
3. 自定义合法实例类型别名
如果不想修改原类,可以单独定义合法实例的联合类型,在所有需要合规实例的场景使用:
// 定义合法的AchDetails实例类型 type ValidAchDetails = | (Omit<AchDetails, 'bankAccountNumber' | 'encryptedBankAccountNumber'> & { bankAccountNumber: string }) | (Omit<AchDetails, 'bankAccountNumber' | 'encryptedBankAccountNumber'> & { encryptedBankAccountNumber: string }); // 使用时用ValidAchDetails约束 function processAch(ach: ValidAchDetails) { // 可安全访问其中一个属性 const accountNumber = ach.bankAccountNumber || ach.encryptedBankAccountNumber; console.log(accountNumber); } processAch({ bankAccountNumber: '123' }); // 合法 processAch({ encryptedBankAccountNumber: 'abc' }); // 合法 processAch({}); // 编译报错
内容的提问来源于stack exchange,提问作者ossiggy
相关产品推荐
相关产品推荐

