TypeScript:如何为泛型类构造函数编写非泛型类型或实现精确类型推断?
解决方案
核心问题是原代码没有把Registry的类型关联到Base子类的泛型参数R上,导致实例的registry属性丢失类型信息。我们可以通过调整类型定义,让RepositoryDescriptor携带值类型信息,并在Registry的get方法中自动注入当前Registry的类型作为实例的泛型参数。
修改后的完整代码
// 让RepositoryDescriptor携带值类型V的信息 type RepositoryDescriptor<V> = { // ctor返回一个泛型类构造函数,该类接受Registry类型R,生成Base<R, V>的实例 ctor: () => new <R>(options: RepositoryOptions<R, V>) => Base<R, V>; }; type RepositoryOptions<R, V> = { n: string; v: V; r: R; }; // Registry的泛型参数改为记录每个key对应的值类型V class Registry<RepoMap extends Record<string, RepositoryDescriptor<any>>> { repositories: RepoMap; constructor(registry: RepoMap) { this.repositories = registry; } // 根据key获取对应的V类型,返回的实例自动关联当前Registry的类型作为R get<K extends keyof RepoMap>( key: K, v: RepoMap[K] extends RepositoryDescriptor<infer V> ? V : never ): ReturnType<RepoMap[K]['ctor']> extends new <R>(opts: any) => infer Instance ? Instance extends Base<infer _, infer __> ? Instance extends Base<R, __> ? Instance : never : never : never { const ctor = this.repositories[key].ctor(); // 这里不用类型断言,TypeScript可以推断出ctor的泛型参数是当前Registry的类型 return new ctor({ r: this, v, n: key as string }) as any; } } class Base<R, V> { registry: R; n: string; v: V; constructor({ n, v, r }: RepositoryOptions<R, V>) { this.registry = r; this.n = n; this.v = v; } } class A<R> extends Base<R, boolean> {} class B<R> extends Base<R, number> {} const registry = new Registry({ A: { ctor: () => A, }, B: { ctor: () => B, }, }); let a = registry.get('A', true); // 类型自动推断为A<typeof registry> let b = registry.get('B', 50); // 类型自动推断为B<typeof registry> let c = a.registry.get('C', true); // 报错:类型“"C"”的参数不能赋给类型“"A" | "B"”的参数 let d = b.registry.get('C', true); // 同样报错,符合预期
关键调整说明
- 泛型化RepositoryDescriptor:让
RepositoryDescriptor<V>明确携带值类型V,其ctor返回的是一个泛型类构造函数,该构造函数接受任意Registry类型R,生成对应的Base<R, V>子类实例。 - Registry的泛型参数优化:将
Registry的泛型改为RepoMap extends Record<string, RepositoryDescriptor<any>>,这样可以追踪每个仓库对应的V类型。 - get方法的类型推断:通过条件类型从
RepoMap[K]中提取V作为参数类型,同时利用ReturnType和infer推断出实例类型,并自动将当前Registry的类型作为Base的R参数注入,无需手动断言。
替代简化方案(更直观)
如果觉得上面的条件类型过于复杂,可以直接在Registry中明确关联实例的R为当前Registry类型,简化get方法的类型定义:
type RepositoryDescriptor<V> = { ctor: new (options: RepositoryOptions<Registry<any>, V>) => Base<Registry<any>, V>; }; class Registry<RepoMap extends Record<string, RepositoryDescriptor<any>>> { repositories: RepoMap; constructor(registry: RepoMap) { this.repositories = registry; } get<K extends keyof RepoMap>(key: K, v: RepoMap[K] extends RepositoryDescriptor<infer V> ? V : never) { const Ctor = this.repositories[key].ctor; return new Ctor({ r: this, v, n: key as string }) as InstanceType<typeof Ctor> extends Base<infer _, infer V> ? Base<this, V> : never; } }
这个方案的效果和之前一致,代码更简洁,适合快速实现。
内容的提问来源于stack exchange,提问作者Bertrand
相关产品推荐
相关产品推荐

