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

net*_*djw 2 typescript typescript-generics

我有一个可以处理本地存储中的数据的服务,还有另一个可以处理远程 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...
}
Run Code Online (Sandbox Code Playgroud)

但我收到这个错误:

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)
Run Code Online (Sandbox Code Playgroud)

RemoteDataService 和 LocalstorageDataService 都使用模型,因此 ProxyService 是我定义模型的最佳位置。因为整个项目中多个地方都用到了ProxyService,而且有多个模型。

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

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)
Run Code Online (Sandbox Code Playgroud)

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

private emptyModel: T = new this.type();
Run Code Online (Sandbox Code Playgroud)

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

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

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

Mar*_*tin 7

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

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

你的type领域undefined自然private emptyModel: T = new this.type();会失败。

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

不是将 Proxy 类创建为单例,而是创建为类。

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

export class ProxyFactoryService {

    constructor(private httpClient: HttpClient) { }

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

那么你的 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...
}
Run Code Online (Sandbox Code Playgroud)

要使用这个

constructor(private proxyService: ProxyService) {}

ngOnInit() {
  const proxy = this.proxyService.proxyFactory(SomeClass);
  proxy.getOne(...);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

const s: new () => String;
const i = new s();
Run Code Online (Sandbox Code Playgroud)

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

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

const s;
const i = new s();
Run Code Online (Sandbox Code Playgroud)

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

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

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