Typescript 扩展具有泛型类型的接口导致约束错误的子类型

Typescript extending interface with generic type causes subtype of constraint error

我有一个抽象的 class,其默认状态值是在 ResourceState 界面中键入的。当我尝试将我的扩展 State 分配给我的摘要 class 的状态 属性 时,出现以下错误。我尝试了很多不同的方法来使用交集,但它仍然不起作用。我想要实现的是,我可以为状态 属性 提供一个通用接口,这样我就可以添加除默认值之外的更多值。

这是我得到的错误:

Type '{ items: never[]; isLoading: false; error: string; }' is not assignable to type 'State'. '{ items: never[]; isLoading: false; error: string; }' is assignable to the constraint of type 'State', but 'State' could be instantiated with a different subtype of constraint 'ResourceState'.

这是我的代码:

export interface BaseItem {
  id: string;
  name: string;
}

export interface ResourceState < Item extends BaseItem > {
  items: Item[];
  selectedItem ? : Item;
  isLoading: boolean;
  error: string;
}

export abstract class Resource < Item extends BaseItem, State extends ResourceState < Item > , Mutations, Getters, Actions > {
  state: State = { // this is where the error occurs
    items: [],
    isLoading: false,
    error: '',
  }
}

当你说 State extends ResourceState<Item> 时,你是说 State 需要符合 ResourceState<Item> 但因为这是一个通用类型参数,它可能不完全是 ResourceState<Item>.

想象一下:

interface MyState extends ResourceState<{ a: 123 }> {
  myCustomRequiredProperty: number
}

现在你将 MyState 作为 State 传递给你的 class,然后你构造状态:

  state: State = { // this is where the error occurs
    items: [],
    isLoading: false,
    error: '',
  }

那么你的 state 变量将不会用 myCustomRequiredProperty 属性 构造,这是必需的。

这是打字稿(隐秘地)试图告诉你的错误。


如果你在 Resource class 中构造状态变量,那么 State 不应该是通用的,因为你在 class 中的可执行代码不能知道可以一般传入的类型的所有属性。所以 State 不应该 extend ResourceState<Item>,它应该恰好是 ResourceState<Item>.

看起来像这样:

export abstract class Resource<Item extends BaseItem> {
  state: ResourceState<Item> = {
    items: [],
    isLoading: false,
    error: '',
  }
}

Playground


但是,如果您希望能够向状态添加属性,那么没有一些初始值就无法做到这一点。您需要以某种方式初始化这些未知属性。为此,您将使用上面的方法,但使用接受任何类型的构造函数是完整状态,然后使用具有该信息的对象填充该类型的未知属性。

export abstract class Resource<
  Item extends BaseItem,
  State extends ResourceState<Item>
> {
  state: State

  constructor(initialState: State) {
    this.state = {
      ...initialState, // Fills in extended properties
      items: [],
      isLoading: false,
      error: '',
    }
  }
}

Playground