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

如何为类型相互依赖的数组数组添加正确的TypeScript类型约束?

解决TypeScript租户类型约束问题

场景与问题背景

我们需要为购物中心创建租户列表,租户分为shop(商店)和restaurant(餐厅)两类,各自有细分类型:

type ShopTypes =
    | `Accessories`
    | `Books`
    | `Clothing`;

type RestaurantTypes =
    | `Dive Bar`
    | `Elevated`
    | `Family`;

type Tenant<TenantCategory extends `shop` | `restaurant` = `shop`> = {
    category: TenantCategory;
    categoryType: TenantCategory extends `shop` ? ShopTypes : RestaurantTypes;
    label: string;
};

为简化编写,希望通过数组数组的形式定义租户,再用函数转换为Tenant类型:

function toTenantList(...items: Array<
    [`shop` | `restaurant`, ShopTypes | RestaurantTypes, string]
>): Array<Tenant> {
    return items.map(([category, categoryType, label]) => ({
        category,
        categoryType,
        label,
    }));
}

const tenants: Array<Tenant> = toTenantList(
    [`shop`, `Accessories`, `Alice's Accessories`],
    [`restaurant`, `Dive Bar`, `Dan's Dive`],
    [`shop`, `Books`, `Barry's Books`],
    [`restaurant`, `Elevated`, `Exquisite Epicure`],
);

此时TypeScript抛出错误:

Type '{ category: "shop" | "restaurant"; categoryType: ShopTypes | RestaurantTypes; label: string; }[]' is not assignable to type 'Tenant<"shop">[]'.
  Type '{ category: "shop" | "restaurant"; categoryType: ShopTypes | RestaurantTypes; label: string; }' is not assignable to type 'Tenant<"shop">'.
    Types of property 'category' are incompatible.
      Type "shop" | "restaurant" is not assignable to type "shop".
        Type "restaurant" is not assignable to type "shop".

核心问题是:TypeScript无法确保shop行对应ShopTypes、restaurant行对应RestaurantTypes,需要设置更精确的类型约束。

解决方案

方法1:用联合类型定义合法的租户条目

先定义每个合法条目类型的联合,让TypeScript识别shop与对应类型、restaurant与对应类型的绑定:

type TenantItem = 
  | [`shop`, ShopTypes, string]
  | [`restaurant`, RestaurantTypes, string];

function toTenantList(...items: TenantItem[]): Tenant[] {
  return items.map(([category, categoryType, label]) => ({
    category,
    categoryType,
    label,
  })) as Tenant[];
}

这种方式会直接校验输入的每个数组是否符合对应类型组合,比如传入['shop', 'Dive Bar', 'xxx']会立刻报错。最后通过类型断言as Tenant[]让TypeScript确认转换后的类型合法性。

方法2:用泛型实现精确类型推导

如果希望函数返回的数组保留每个租户的具体类型,可以用泛型处理:

type TenantItem = 
  | [`shop`, ShopTypes, string]
  | [`restaurant`, RestaurantTypes, string];

function toTenantList<T extends TenantItem>(...items: T[]): {
  [K in keyof T]: T[K] extends [`shop`, infer S, string] ? Tenant<'shop'> : Tenant<'restaurant'>
} {
  return items.map(([category, categoryType, label]) => ({
    category,
    categoryType,
    label,
  })) as any;
}

// 此时tenants的类型自动推导为(Tenant<'shop'> | Tenant<'restaurant'>)[]
const tenants = toTenantList(
    [`shop`, `Accessories`, `Alice's Accessories`],
    [`restaurant`, `Dive Bar`, `Dan's Dive`],
    [`shop`, `Books`, `Barry's Books`],
    [`restaurant`, `Elevated`, `Exquisite Epicure`],
);

这种方式无需手动指定返回类型,TypeScript会根据输入条目自动推导对应的Tenant子类型,类型安全性更高。

方法3:拆分Tenant为明确的联合类型

调整Tenant类型为两个子类型的联合,让输入与输出的类型匹配更直接:

type ShopTenant = {
  category: 'shop';
  categoryType: ShopTypes;
  label: string;
};

type RestaurantTenant = {
  category: 'restaurant';
  categoryType: RestaurantTypes;
  label: string;
};

type Tenant = ShopTenant | RestaurantTenant;

type TenantItem = 
  | [`shop`, ShopTypes, string]
  | [`restaurant`, RestaurantTypes, string];

function toTenantList(...items: TenantItem[]): Tenant[] {
  return items.map(([category, categoryType, label]) => ({
    category,
    categoryType,
    label,
  }));
}

这种方式不需要额外类型断言,TypeScript可直接识别转换后的对象符合Tenant类型。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 18:57:42