U 型(通用)不可分配给未知类型

Type U (generic) is not assignable to type unknown

我有一个通用存储库 class,每个存储库都有自己的子存储库。 子存储库并由记录 {String: Repository} 保存。 它看起来像这样:

export class Repository<T> {
  private readonly collection: Record<string, T> = {};
  private readonly subrepositories: Record<string, Repository<unknown>> = {};
.
.
  subrepository<U>(docName: string) {
    if (!this.subrepositories[docName]) {
      this.subrepositories[docName] = new Repository<U>();
    }
    return this.subrepositories[docName];
  }


在这一行中:this.subrepositories[docName] = new Repository<U>(); 我收到打字稿错误:

TS2322: Type 'Repository<U>' is not assignable to type 'Repository<unknown>'.
Types of property 'set' are incompatible.
Type '(id: string, data: U) => void' is not assignable to type '(id: string, data: unknown) => void'.
Types of parameters 'data' and 'data' are incompatible.
Type 'unknown' is not assignable to type 'U'.
'U' could be instantiated with an arbitrary type which could be unrelated to 'unknown'.

我设法克服这个问题的唯一方法是先将 this.subrepositories 转换为 unknown,然后再转换为 Record<string, Repository<U>,就像这样:

  subrepository<U>(docName: string) {
    if (!this.subrepositories[docName]) {
      (this.subrepositories as unknown as Record<string, Repository<U>>)[
        docName
      ] = new Repository<U>();
    }
    return this.subrepositories[docName];
  }

但这真的是解决方案吗?

您的类型可能在类型参数 T 上是不变的。如果您在协变位置(例如字段)和逆变位置(例如函数参数)中都使用类型参数,则会发生这种情况。您可以了解有关方差的更多信息 here

export class Repository<T> {
  private readonly collection: Record<string, T> = {};
  private readonly subrepositories: Record<string, Repository<unknown>> = {};
  fn!: (p: T) => void; // Remove this and asssignability works
  subrepository<U>(docName: string) {
    if (!this.subrepositories[docName]) {
      this.subrepositories[docName] = new Repository<U>();
    }
    return this.subrepositories[docName];
  }
}

Playground Link

这不起作用的原因是它实际上不是类型安全的。因为您可以传入函数不期望的类型。

  sampleMethod() {
      const rep = this.subrepository<{ a: number} >("foo");
      rep.fn =  p => p.a.toFixed();

      
      const repAlias = this.subrepository<{ a: string} >("foo");
      repAlias.fn({ a: "" }) // 
  }

Playground Link

此外,您应该避免在单个位置使用类型参数,因为它们不提供很多类型安全性,通常只是屏蔽类型断言