TypeScript 适配器映射场景下如何避免方法同名的类被类型混淆
解决方案
TypeScript 默认采用结构化类型系统,只要两个类型的公开成员结构完全一致,就会判定为类型兼容,这是示例中 CatAdapter 可以被塞进 DogAdapter 数组的核心原因。
方案1:给适配器类添加私有标识属性
成本最低的修改方案,私有属性不会参与结构化类型的兼容判定,只要两个类的私有属性不同,就会被判定为完全不兼容的类型。
修改后的代码示例:
enum EAnimals { cat = 'cat', dog = 'dog' } interface IAnimal { talk(): void } class CatAdapter implements IAnimal { // 新增私有标识属性,值和对应枚举项绑定 private readonly __type = EAnimals.cat; constructor(name: string) {} public talk (): void { console.log('meow') } } class DogAdapter implements IAnimal { // 私有标识和狗的枚举项绑定 private readonly __type = EAnimals.dog; constructor(name: string) {} public talk (): void { console.log('woof') } } type TAnimalToAdapter = { [EAnimals.cat]: CatAdapter[], [EAnimals.dog]: DogAdapter[], } const Adapters: TAnimalToAdapter = { [EAnimals.cat]: [ new CatAdapter('first breed'), new CatAdapter('second breed'), ], [EAnimals.dog]: [ new DogAdapter('first breed'), new CatAdapter('second breed'), // 此处会直接抛出TS类型错误 ], }
方案2:基于泛型自动推导的映射表实现
如果想要更统一、更低出错概率的适配器映射管理,可以用泛型封装工厂方法,自动约束枚举和适配器的对应关系,避免手动定义类型的重复劳动:
// 泛型工厂,自动绑定枚举值和适配器构造函数的对应关系 function createAdapterMap<T extends Record<string, new (...args: any[]) => IAnimal>>(adapterConfig: T) { return <K extends keyof T>(key: K, ...constructorParams: ConstructorParameters<T[K]>): InstanceType<T[K]> => { return new adapterConfig[key](...constructorParams) as InstanceType<T[K]> } } // 定义枚举和适配器构造函数的映射 const adapterFactory = createAdapterMap({ [EAnimals.cat]: CatAdapter, [EAnimals.dog]: DogAdapter, }) // 生成适配器映射表,自动推导类型,不会出现类型混淆 const Adapters: TAnimalToAdapter = { [EAnimals.cat]: [ adapterFactory(EAnimals.cat, 'first breed'), adapterFactory(EAnimals.cat, 'second breed'), ], [EAnimals.dog]: [ adapterFactory(EAnimals.dog, 'first breed'), // 此处如果传EAnimals.cat会直接在参数阶段报错,根本到不了赋值阶段 adapterFactory(EAnimals.dog, 'second breed'), ], }
该方案不仅可以避免赋值时的类型混淆,连构造适配器的时候传错枚举都会提前报错,进一步降低出错概率。
内容的提问来源于stack exchange,提问作者SpekalsG3
相关产品推荐
相关产品推荐

