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

如何将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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 10:10:04