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

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); // 同样报错,符合预期

关键调整说明

  1. 泛型化RepositoryDescriptor:让RepositoryDescriptor<V>明确携带值类型V,其ctor返回的是一个泛型类构造函数,该构造函数接受任意Registry类型R,生成对应的Base<R, V>子类实例。
  2. Registry的泛型参数优化:将Registry的泛型改为RepoMap extends Record<string, RepositoryDescriptor<any>>,这样可以追踪每个仓库对应的V类型。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 04:11:10