TypeScript泛型继承传递性问题:如何实现可传递的extends约束?
TypeScript泛型约束报错:
Type 'R' does not satisfy the constraint 'LibModel' 问题代码
模型类定义
// 从数据库读取的模型 class LibModel { id: number; find<T extends LibModel>(params: Partial<T>): T { return params as T; // 伪实现,实际从数据库读取 } } // 所有优先级队列的模型 class PriorityQueueModel extends LibModel { priority: number; } class AmazonQueueModel extends PriorityQueueModel { amazonId: number; } class EbayQueueModel extends PriorityQueueModel { ebayId: number; }
仓库类定义及报错场景
// 管理所有类型的模型 abstract class BaseRepository<T extends LibModel> { Model: T; constructor(Model: T) { this.Model = Model; } public findById(id: number) { return this.Model.find({ id }); } } // 仅管理优先级队列模型 class GenericQueueRepository< R extends typeof PriorityQueueModel > extends BaseRepository<R> {} // 此处报错:Type 'R' does not satisfy the constraint 'LibModel' const AmazonPriorityQueue = new GenericQueueRepository<typeof AmazonQueueModel>( AmazonQueueModel ); const amazonItem = AmazonPriorityQueue.findById(1); // { priority, amazonId }
报错原因
根本不是“约束传递性失效”,而是混淆了类的实例类型和类的构造函数类型:
PriorityQueueModel extends LibModel是实例类型的继承关系,指PriorityQueueModel的实例是LibModel实例的子类。typeof PriorityQueueModel是构造函数类型(本质是new () => PriorityQueueModel),代表创建PriorityQueueModel实例的构造函数本身的类型。BaseRepository<T extends LibModel>要求泛型参数T是实例类型,但GenericQueueRepository的R是构造函数类型,两者类型层级完全不匹配,自然无法满足约束。
解决方案
方案一:调整BaseRepository接收构造函数类型
把BaseRepository的泛型参数改为构造函数类型,通过InstanceType<T>获取实例类型:
abstract class BaseRepository<T extends typeof LibModel> { Model: T; constructor(Model: T) { this.Model = Model; } public findById(id: number): InstanceType<T> { return this.Model.find({ id }) as InstanceType<T>; } } class GenericQueueRepository< R extends typeof PriorityQueueModel > extends BaseRepository<R> {} const AmazonPriorityQueue = new GenericQueueRepository<typeof AmazonQueueModel>(AmazonQueueModel); const amazonItem = AmazonPriorityQueue.findById(1); // 类型自动推断为AmazonQueueModel
方案二:保持BaseRepository的实例类型约束,修正仓库类的泛型关联
明确GenericQueueRepository的泛型参数是实例类型,同时约束其构造函数继承自PriorityQueueModel:
abstract class BaseRepository<T extends LibModel> { Model: typeof LibModel; constructor(Model: typeof LibModel) { this.Model = Model; } public findById(id: number): T { return this.Model.find({ id }) as T; } } class GenericQueueRepository<T extends PriorityQueueModel> extends BaseRepository<T> { constructor(Model: typeof PriorityQueueModel) { super(Model); } } const AmazonPriorityQueue = new GenericQueueRepository<AmazonQueueModel>(AmazonQueueModel); const amazonItem = AmazonPriorityQueue.findById(1); // 类型为AmazonQueueModel
额外说明
添加// @ts-expect-error后代码能运行,是因为TypeScript在运行时只关心值的结构,AmazonQueueModel作为构造函数确实有find方法,但类型层面的不匹配会导致后续类型推断失效,比如amazonItem的类型无法正确推断为AmazonQueueModel,所以不建议忽略报错。
内容的提问来源于stack exchange,提问作者Milan Smolík
相关产品推荐
相关产品推荐

