如何从 TypeScript 中的泛型类型创建实例?

How to create an instance from a generic type in TypeScript?

我有一项服务可以处理本地存储中的数据,我还有一项服务可以处理远程 API 查询。当我定义一个数据模型时,我给它模型是否使用本地存储。

现在我尝试构建一个代理服务,它可以根据模型定义决定从本地存储还是从远程请求数据API。

现在我有这个代码:

export class ProxyService<T> {
  public type: new () => T;
  private emptyModel: T = new this.type();
  private localstorageService: LocalstorageDataService<T> =
    new LocalstorageDataService(this.type, this.httpClient);
  private remoteDataService: RemoteDataService<T> = new RemoteDataService(this.type, this.httpClient);

  constructor(
    private httpClient: HttpClient,
  ) { }

  getOne(id: number): Observable<T> {
    if (this.emptyModel.use_localstorage) {
      return of(this.localstorageService.findById(id));
    } else {
      return this.remoteDataService.getOne(id).pipe(map((result: T) => result));
    }
  }

  // other proxy functions are here...
}

但是我得到这个错误:

Error: Uncaught (in promise): TypeError: this.type is not a constructor
TypeError: this.type is not a constructor
    at new ProxyService (proxy.service.ts:19)

RemoteDataService 和 LocalstorageDataService 都使用一个模型,所以 ProxyService 是我定义一个的最佳位置。因为ProxyService在整个项目中多处使用,多模型

顺便说一句,第 19 行是上面的代码:

private emptyModel: T = new this.type();

我试图在构造函数中修复它:

export class ProxyService<T> {
  // ...
  constructor(
    public type: new () => T;
    private httpClient: HttpClient,
  ) { }
  // ...

但在这种情况下,我需要在调用ProxyService 时定义一个模型,并且我不想在使用ProxyService 时每次都定义一个模型。在这种情况下,我不能像下一个代码那样使用 ProxyService,但这是我想要使用的方式:

export class HelperService<T> {
  constructor(
    private proxy: ProxyService<T>,
  ) { }

知道如何解决这个问题吗?

您似乎将 TypeScript 类型注释与 运行time JavaScript 具体实现混淆了。

在 TypeScript 中你不能做 new T(),因为 T 只是一个类型注释,没有具体的实现。

你的 type 字段 undefined 所以自然 private emptyModel: T = new this.type(); 会失败。

我认为您找到了正确的解决方案。

创建代理 class 不是单例,而是 class。

然后创建一个公开返回实例的工厂方法的服务。

export class ProxyFactoryService {

    constructor(private httpClient: HttpClient) { }

    proxyFactory<T>(newable: new() => T) {
      const instance = new newable(); 
      return new ProxyService<T>(this.httpClient, instance);
    }
}

那么您的 ProxyService 可能类似于:

 export class ProxyService<T> {
   private localstorageService: LocalstorageDataService<T>;
   private remoteDataService: RemoteDataService<T>;

   constructor(private httpClient: HttpClient, private instance: T) {
     this.localstorageService = new LocalstorageDataService(this.instance, this.httpClient);
     this.remoteDataService = new RemoteDataService(this.instance, this.httpClient)
   }

   getOne(id: number): Observable<T> {
     if (this.instance.use_localstorage) { // we probably want to make sure T extends {use_localStorage: boolean}
       return of(this.localstorageService.findById(id));
     } else {
       return this.remoteDataService.getOne(id).pipe(map((result: T) => result));
    }
  }
  // other proxy functions are here...
}

使用这个

constructor(private proxyService: ProxyService) {}

ngOnInit() {
  const proxy = this.proxyService.proxyFactory(SomeClass);
  proxy.getOne(...);
}

总是问自己一个问题:删除注释后如何工作?

当 TypeScript 编译时,所有类型注释都被删除——这些只是设计时静态类型注释。

另一种思考方式是这种简化——我们将移除泛型并使用具体类型。但本质上这归结为你想做的事情:

const s: new () => String;
const i = new s();

所以 s 是一个构造函数,当被调用时 returns 是一个类型 String。这将不起作用,因为 s 未定义。我们知道它应该是什么类型,但实际上它是未定义的。这一切都是非常人为的——但我希望这会有所帮助。

如果我们查看上面的编译器输出,应该会更清楚为什么这行不通。

const s;
const i = new s();

幸运的是,在我们真正有机会 运行 这段代码之前,TypeScript 会出错。如果你的 IDE 设置正确,你应该在设计时收到很多警告和错误。

我知道这可能令人沮丧和困惑,尤其是来自 C# 和 Java 等语言,其中 CIL/CLR 是静态类型感知的。在 TypeScript 中没有 运行time,它只是被编译成 JavaScript。

静态类型和类型注释在 运行 时不可用。