如何让TypeScript类型系统在该示例中统一关联表类型
解决Zapatos库中表类型与对应_source类型的关联转换问题
问题场景
使用Zapatos库建模数据库表,现有table_b、table_c及其对应table_b_source、table_c_source的类型定义,需要实现Thing类的convert函数:接收Subset(table_b/table_c)对应的Selectable对象和common字符串,返回对应${T}_source的Selectable类型。但当前代码出现类型不匹配报错,编译器无法识别T与${T}_source的关联关系。
库固定定义代码
// this cannot change -- this is how the library (zapatos in this case) defines things namespace table_a { export type Table = 'table_a'; export interface Selectable { random1: number; } } namespace table_b { export type Table = 'table_b'; export interface Selectable { field1: number; } } namespace table_c { export type Table = 'table_c'; export interface Selectable { field2: number; } } namespace table_b_source { export type Table = 'table_b_source'; export interface Selectable { field1: number; common: string; } } namespace table_c_source { export type Table = 'table_c_source'; export interface Selectable { field2: number; common: string; } } type Table = | table_a.Table | table_b.Table | table_c.Table | table_b_source.Table | table_c_source.Table; type SelectableForTable<T extends Table> = { table_a: table_a.Selectable; table_b: table_b.Selectable; table_c: table_c.Selectable; table_b_source: table_b_source.Selectable; table_c_source: table_c_source.Selectable; }[T];
待实现代码及报错
// This we have more flexibility with type Subset = Extract<Table, 'table_b' | 'table_c'>; class Thing<T extends Subset> { // how can I get this to work? convert(obj: SelectableForTable<T>, common: string): SelectableForTable<`${T}_source`> { return { ...obj, common, }; } }
报错信息:
Type 'SelectableForTable<T> & { common: string; }' is not assignable to type 'SelectableForTable<`${T}_source`>'. Type 'SelectableForTable<T> & { common: string; }' is not assignable to type 'table_b_source.Selectable & table_c_source.Selectable'. Type 'SelectableForTable<T> & { common: string; }' is not assignable to type 'Selectable'
解决方案
核心思路是建立Subset类型与对应_source表类型的明确映射,让编译器能清晰识别两者的关联,而非依赖模板字符串的自动推断(TypeScript泛型中模板字符串类型的关联推断能力有限)。
步骤1:创建类型映射表
定义一个映射类型,明确每个Subset表对应的_source表类型:
// 建立Subset表到对应_source表的明确映射 type SourceTableMap = { table_b: table_b_source.Table; table_c: table_c_source.Table; };
步骤2:修改Thing类实现
基于映射表重构Thing类,用映射后的类型约束返回值,并通过类型断言让编译器确认转换的安全性(因为我们明确知道扩展后的对象结构与目标Selectable类型完全匹配):
// 用映射表的键重新定义Subset,确保类型关联准确 type Subset = keyof SourceTableMap; // 可选:封装Source表对应的Selectable类型,简化代码 type SourceSelectable<T extends Subset> = SelectableForTable<SourceTableMap[T]>; class Thing<T extends Subset> { convert(obj: SelectableForTable<T>, common: string): SourceSelectable<T> { return { ...obj, common, } as SourceSelectable<T>; } }
原理说明
- 原代码中
${T}_source是模板字符串生成的计算类型,编译器无法自动将其与SelectableForTable中的键建立精确关联,也无法确认交叉类型SelectableForTable<T> & { common: string }与目标Selectable接口的兼容性。 - 通过
SourceTableMap的硬绑定,我们给编译器提供了明确的类型关联规则,再结合类型断言(安全的,因为我们清楚业务逻辑上的结构匹配),就能解决类型不匹配的问题。
内容的提问来源于stack exchange,提问作者A Question Asker
相关产品推荐
相关产品推荐

