如何将TypeScript泛型应用于多属性定义的食材数据对象?
用TypeScript泛型优化食材管理器的类型设计
1. 统一重复的Profile结构
先把所有食材Profile共有的type和state属性抽成通用接口,避免重复定义:
// 通用基础Profile结构,抽离重复的type/state属性 interface SpecPrimitive<T extends string> { type: T; // 用泛型约束type为具体字符串字面量,比如"flour"|"salt" state: "fresh" | "expired" | "stored"; // 假设所有食材共享这几种状态 }
基于这个通用接口定义具体的食材Profile:
// 面粉专属Profile,继承基础结构并添加自定义字段 interface FlourProfile extends SpecPrimitive<"flour"> { glutenContent: number; // 面筋含量 millingMethod: "stone" | "roller"; // 研磨方式 } // 盐专属Profile interface SaltProfile extends SpecPrimitive<"salt"> { saltType: "sea" | "table" | "rock"; // 盐的种类 iodineContent: boolean; // 是否含碘 }
2. 改造Ingredient为泛型接口
将原Ingredient的data字段改为泛型参数对应的类型,让每个Ingredient实例的data类型完全匹配对应食材的Profile,避免联合类型的模糊性:
interface IngredientNote { title: string; description: string; } // 省略的基础接口示例 interface Nutrition { calories: number; protein: number; } type IngredientCategory = "pantry" | "fridge" | "freezer"; // 泛型Ingredient接口,P代表具体的Profile类型 interface Ingredient<P extends SpecPrimitive<string>> { name: string; brand: string; id: string; photo?: string; nutrition?: Nutrition; notes?: IngredientNote[]; data?: P; // 现在data的类型是具体Profile,不再是联合类型 locations: IngredientCategory[]; }
使用示例
创建具体食材实例时,指定泛型参数,TypeScript会自动校验data字段结构:
// 面粉食材实例 const wheatFlour: Ingredient<FlourProfile> = { name: "高筋面粉", brand: "金像", id: "flour_001", locations: ["pantry"], data: { type: "flour", state: "fresh", glutenContent: 12.5, millingMethod: "roller" } }; // 盐食材实例 const seaSalt: Ingredient<SaltProfile> = { name: "海盐", brand: "无印良品", id: "salt_001", locations: ["pantry"], data: { type: "salt", state: "stored", saltType: "sea", iodineContent: false } };
3. 自定义泛型工具类型
基于泛型Ingredient,可创建实用的泛型工具简化开发:
工具1:强制data字段必填的Ingredient
如果需要确保食材有完整的Profile数据,结合内置工具和自定义泛型实现:
// 自定义泛型工具:让Ingredient的data字段必填 type IngredientWithRequiredData<P extends SpecPrimitive<string>> = Required<Pick<Ingredient<P>, "data">> & Omit<Ingredient<P>, "data">; // 使用示例:必须提供data字段,否则报错 const requiredFlour: IngredientWithRequiredData<FlourProfile> = { name: "低筋面粉", brand: "王后", id: "flour_002", locations: ["pantry"], data: { type: "flour", state: "fresh", glutenContent: 8.0, millingMethod: "stone" } };
工具2:提取所有Profile的type类型集合
创建允许所有食材type的联合类型,用于参数校验等场景:
// 自定义泛型工具:从Profile中提取type字段的类型 type ExtractProfileType<P extends SpecPrimitive<string>> = P["type"]; // 所有食材type的联合类型 type AllIngredientTypes = ExtractProfileType<FlourProfile | SaltProfile>; // 等价于 "flour" | "salt" // 用该类型做参数校验 function getIngredientByType(type: AllIngredientTypes): Ingredient<any> | undefined { // 业务逻辑实现 return undefined; }
工具3:根据type自动推断Profile类型
通过判别式联合类型的泛型版本,让TypeScript根据type字段自动确定data类型:
// 定义所有Profile的联合类型 type AllIngredientProfiles = FlourProfile | SaltProfile; // 泛型工具:根据type获取对应的Profile type ProfileByType<T extends AllIngredientProfiles["type"]> = Extract<AllIngredientProfiles, { type: T }>; // 对应的Ingredient类型 type TypedIngredient<T extends AllIngredientProfiles["type"]> = Ingredient<ProfileByType<T>>; // 使用示例:指定type后,data类型自动推断,错误字段会直接报错 const typedSalt: TypedIngredient<"salt"> = { name: "加碘盐", brand: "中盐", id: "salt_002", locations: ["pantry"], data: { type: "salt", state: "fresh", saltType: "table", iodineContent: true } };
总结
通过以上改造,你可以:
- 消除Profile间的重复代码,统一维护基础结构
- 让Ingredient类型更精确,避免联合类型的模糊性
- 自定义泛型工具适配不同业务场景,提升类型复用性
内容的提问来源于stack exchange,提问作者Optiq
相关产品推荐
相关产品推荐

