如何在 类 上实现类型?

how can I implement types on classes?

我有这个基本的 class:


type Action = { type: string; payload?: {} }
type Dispatch<ActionType extends Action> = (action: ActionType) => void


type Actions = 
  |  { type: '@ADD', payload: { item: string, id: string } }
  |  { type: '@REM', payload: { id: string } }

class CardActions implements ActionClass<Actions> { // this isn't working as I expect, see below
  constructor(private dispatch: Dispatch<Actions>) {}

  disp(action) {} // this isn't working
}

尝试实现一个接口对构造函数和 disp 函数都不起作用,它总是有错误:

Index signature is missing in type 'CardsActions'

Class 'CardActions' incorrectly implements interface 'ActionClass'.

export interface ActionClass<ActionType extends Action> {
  // new(dispatch: string): any;
  [actionKey: string]: (action: ActionType['type']) => void
}

我希望它像任何功能一样工作,我误解了吗?

预期

interface Func {
  (action: Actions['type']): void
}

const func: Func = (action) => {} // this will work

playground

为什么他们不像彼此一样工作?我怎样才能让它发挥作用?

对于这个答案,我将使用略微修改的 ActionClass 版本,它删除了泛型,因为它们与您 运行 遇到的特定问题无关:

interface ActionClass {
  [actionKey: string]: (action: Actions['type']) => void
}

TypeScript 的一个当前限制是,当 class 实现 interface 时,编译器不使用 contextually type the members of the class. In class Foo implements Bar {...}, the compiler will infer a type for Foo without consulting Bar at all; and only afterward will it check that type against Bar. See microsoft/TypeScript#32082 的接口定义以及其中的许多相关问题阅读更多相关信息。

目前,如果您想让 class Foo implements Bar {...} 工作,您将需要手动显式地为 Foo 的成员提供编译器所需的一切,以便 Foo 的推断类型可分配给 Bar.

现在 CardActions 被推断为就好像你这样写:

class CardActions {
  constructor(private dispatch: Dispatch<Actions>) { }
  disp(action) { }
}

因为你没有给CardActions一个index signature,编译器不会推断出一个。因此 CardActions 不被视为可分配给 ActionClass,它确实有一个:

class CardActions implements ActionClass { // error
  constructor(private dispatch: Dispatch<Actions>) { } 
  disp(action) { }
}

另外,disp()action参数被推断为any,因为编译器根本没有参考ActionClass,而你没有注释action.

因此,这里修复的第一步是为 CardActions 提供显式索引签名,并为 disp()action 参数提供显式类型:

class CardActions implements ActionClass {
  constructor(private dispatch: Dispatch<Actions>) { } // error
  [actionKey: string]: (action: Actions['type']) => void;
  disp(action: Actions['type']) { }
}

现在我们有一个新的错误。


注意 ActionClass 有一个字符串索引签名。这意味着 ActionClass 对象的 所有 属性必须是 (action: Actions['type']) => void 类型的函数。但是在您的构造函数中,您已将 private 关键字放在 dispatch 参数上,使其成为 parameter property。那是 shorthand 类似的东西:

class CardActions implements ActionClass {
  private dispatch: Dispatch<Actions>; // error
  constructor(dispatch: Dispatch<Actions>) {
    this.dispatch = dispatch;
  }
  [actionKey: string]: (action: Actions['type']) => void;
  disp(action: Actions['type']) { }
}

这就是你的问题。 dispatch 属性 是 Dispatch<Actions> 类型,但索引签名要求每个 属性 都可以分配给 (action: Actions['type']) => void。并且 Dispatch<Actions> 不可分配给那个(参数类型错误)。

我不确定解决此问题的正确方法是什么,因为我不确定您是真的想要书面的索引签名还是真的想要 dispatch 属性。但是你不可能两者都没有错误。让我们删除 dispatch 属性 以查看错误是否消失:

class CardActions implements ActionClass {
  constructor(dispatch: Dispatch<Actions>) { }
  [actionKey: string]: (action: Actions['type']) => void;
  disp(action: Actions['type']) { }
}

万岁,没有错误!可能有一百种不同的可能方法可以代替这个,但探索这些可能超出了这个问题的范围。


好了。如果你想让 class 实现一个带有字符串索引签名的接口,你需要在 class 定义中显式声明索引签名,并且你需要确保每个已知的 属性 的 class 与索引签名兼容。

Playground link to code