如何在TypeScript中为派生类型添加新取值并维护相关类型映射?
如何在TypeScript中为派生类型添加新取值并维护相关类型映射?
嘿,看你这套TypeScript类型系统搭得有模有样的,但每次加新动物类型时是不是得改好几个地方?一会儿改枚举,一会儿改映射接口,稍不留神就漏改了?我来给你支个招,让新增类型的维护工作轻松不少。
先把你现有的代码贴出来,方便咱们对照着说:
interface CommonAnimalProps { common: { size: number, weight: number, origin: string, } } interface DogProps extends CommonAnimalProps { name: string } interface CatProps extends CommonAnimalProps { furious: boolean } interface BirdProps extends CommonAnimalProps { canFly: boolean } export enum AnimalType { dog = "dog", cat = "cat", bird = "bird", } export interface AnimalTypeToAnimalPropsMap { [AnimalType.dog]: (DogProps)[], [AnimalType.cat]: (CatProps)[], [AnimalType.bird]: (BirdProps)[] } export type AllAnimalValues<T> = T[keyof T] export type AllAnimalTypesArray = AllAnimalValues<AnimalTypeToAnimalPropsMap> export type AllAnimalTypes = AllAnimalValues<AnimalTypeToAnimalPropsMap>[0] export type AllAnimalTypesArrayDiscriminating = AllAnimalTypes[]
现有结构的维护痛点
目前你这套结构的问题很明显:新增动物类型时必须同步修改两处——既要在AnimalType枚举里加新值,又要在AnimalTypeToAnimalPropsMap里加对应的类型映射,万一漏改其中一处,类型系统就会出问题。
优化方案:用单一数据源驱动所有类型推导
咱们可以把「区分联合类型」作为单一可信数据源,让枚举、类型映射、数组类型都从这个联合类型自动推导。这样新增动物时,只需要加一个接口就行,其他所有类型都会自动更新。
优化后的代码是这样的:
interface CommonAnimalProps { common: { size: number, weight: number, origin: string, } } // 每个动物接口都加一个唯一的type字面量属性,作为区分联合类型的标识 interface DogProps extends CommonAnimalProps { type: 'dog'; name: string; } interface CatProps extends CommonAnimalProps { type: 'cat'; furious: boolean; } interface BirdProps extends CommonAnimalProps { type: 'bird'; canFly: boolean; } // 新增动物?只需要加这一个接口就行! interface FishProps extends CommonAnimalProps { type: 'fish'; waterType: 'fresh' | 'salt'; // 新增动物的专属属性 } // 定义所有动物的区分联合类型——这就是咱们的单一数据源 type AllAnimalTypes = DogProps | CatProps | BirdProps | FishProps; // 自动推导动物类型的字面量集合(代替原来的枚举,也可以转成枚举) type AnimalType = AllAnimalTypes['type']; // 自动推导类型到数组的映射,完全不用手动维护 type AnimalTypeToAnimalPropsMap = { [K in AnimalType]: Extract<AllAnimalTypes, { type: K }>[]; }; // 原来的工具类型可以大幅简化 type AllAnimalTypesArray = AllAnimalTypes[]; type AllAnimalTypesArrayDiscriminating = AllAnimalTypes[];
优化后的好处
- 新增类型超省心:只需要新增一个带
type标识的接口,其他所有类型(包括AnimalType、映射类型、数组类型)都会自动同步更新,再也不用怕漏改 - 类型收窄贼方便:因为用了区分联合类型,你在处理动物实例时,TypeScript会自动根据
type属性做类型收窄,不用手动类型断言:function feedAnimal(animal: AllAnimalTypes) { switch (animal.type) { case 'dog': console.log(`喂${animal.name}吃狗粮`); // 这里自动推断为DogProps break; case 'fish': console.log(`喂鱼吃饲料,水质要求:${animal.waterType}`); // 自动推断为FishProps break; // 其他动物类型同理 } } - 类型安全有保障:如果你的
type标识写错了,TypeScript会直接报错,从源头避免错误
如果你还想用枚举
要是你习惯用枚举而不是字面量类型,也可以从推导出来的AnimalType字面量类型生成枚举,还能加个类型断言确保枚举值和字面量一致:
export enum AnimalType { Dog = 'dog', Cat = 'cat', Bird = 'bird', Fish = 'fish' } // 可选:检查枚举值是否和AnimalType字面量完全匹配,不匹配就报错 type AssertAnimalTypeMatches = AnimalType[keyof AnimalType] extends AnimalType ? true : never;
这样一来,你整个类型系统的维护成本就大大降低了,新增动物类型再也不用东改西改啦!
备注:内容来源于stack exchange,提问作者four-eyes
相关产品推荐
相关产品推荐

