如何为类型相互依赖的数组数组添加正确的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
相关产品推荐
相关产品推荐

