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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 21:07:27