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

基于键类型自动收窄值类型的TypeScript实现问询

问题

我定义了以下类型和Save函数:

type Collection<K extends string, EntityType> = {
  save(key: K, value: EntityType): void;
  get(key: K, value: EntityType): EntityType[];
  all(): EntityType[];
};

type Domain = {
  [key: string]: Collection<any, any>;
};

function Save<D extends Domain, K extends string, V>(
  dataStore: D,
  key: K,
  value: V
) {
  if (key.startsWith("orders/")) {
    dataStore.orders.save(key, value);
  } else if (key.startsWith("customers/")) {
    dataStore.customers.save(key, value);
  }
  //...
}

我希望按如下方式使用Save()函数:使其能够通过查找CommerceDomain中的Collection<>成员,自动将Save()调用中的value参数类型收窄为对应的实体类型:

type CustomerId = `customers/${string}`;
type OrderId = `orders/${string}`;
type ProductId = `products/${string}`;

interface Customer { customer: string }
interface Order { order: string }
interface Product { product: string }
declare const customer: Customer;
declare const order: Order;
declare const product: Product;

type CommerceDomain = {
  orders: Collection<OrderId, Order>;
  customers: Collection<CustomerId, Customer>;
  products: Collection<ProductId, Product>;
};

declare const store: CommerceDomain;

save(store, "orders/1", {/* 应自动推导为Order类型 */});

save(store, "customers/1", {/* 应自动推导为Customer类型 */});

请问如何实现这一需求?


解决方案

要实现根据key自动推导value的类型,需修改Save函数的泛型约束,利用条件类型和索引类型建立key与对应Collection实体类型的关联,同时优化函数逻辑的扩展性。

修改后的完整实现

type Collection<K extends string, EntityType> = {
  save(key: K, value: EntityType): void;
  get(key: K): EntityType[]; // 修正原get方法的多余参数问题
  all(): EntityType[];
};

type Domain = {
  [key: string]: Collection<string, any>;
};

// 工具类型:从Domain中提取key前缀对应的实体类型
type EntityForKey<D extends Domain, K extends string> = 
  K extends `${infer Prefix}/${string}` 
    ? Prefix extends keyof D 
      ? D[Prefix] extends Collection<any, infer E> 
        ? E 
        : never 
      : never 
    : never;

function save<
  D extends Domain, 
  K extends string,
  V extends EntityForKey<D, K>
>(
  dataStore: D,
  key: K,
  value: V
) {
  // 提取key前缀并调用对应Collection的save方法,无需硬编码判断
  const prefix = key.split('/')[0] as keyof D;
  dataStore[prefix].save(key as any, value);
}

核心逻辑说明

  1. 工具类型EntityForKey:
    • 通过模板字符串类型${infer Prefix}/${string}提取key中的前缀(如orders/1中的orders)
    • 验证前缀是否属于Domain的键,再从对应Collection中提取实体类型E,完成类型映射
  2. 泛型约束优化:
    • 将V的类型约束为EntityForKey<D, K>,TypeScript会自动根据传入的key推导出value必须匹配的实体类型
  3. 函数逻辑简化:
    • 移除硬编码的if-else判断,通过动态提取前缀调用对应Collection的方法,新增products这类集合时无需修改函数逻辑

使用验证

现在调用save函数时,TypeScript会自动完成类型推导与校验:

// 正确:value自动推导为Order类型
save(store, "orders/1", { order: "123" });

// 正确:value自动推导为Customer类型
save(store, "customers/1", { customer: "Alice" });

// 错误:类型不匹配,TypeScript会直接报错
save(store, "orders/1", { customer: "Bob" });

内容的提问来源于stack exchange,提问作者Serguzest

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 05:10:35