0

我在一些我无法修改的第三方库中有这个声明的类:

export declare class KeyboardSensor implements SensorInstance {
  private props;
  constructor(props: KeyboardSensorProps);
  private attach;
  private handleStart;
  private detach;
  static activators: {
      eventName: "onKeyDown";
      handler: (event: React.KeyboardEvent, { keyboardCodes, onActivation, }: KeyboardSensorOptions) => boolean;
  }[];
}

我想以这种方式自定义它,使用从该声明扩展的新类:

export class CustomKeyboardSensor extends KeyboardSensor {
  public static activators = [
    {
      eventName: "onKeyDown" as const,
      moreCode: "...",
    },
    {
      eventName: "onKeyUp" as const,
      moreCode: "...",
    },
  ];
}

我显然收到类型属性不兼容的错误:

属性“eventName”的类型不兼容。

如何为可以修改类型的声明类定义新的接口/eventName类型?

4

2 回答 2

0

TypeScript 中的类继承不允许派生类比基类更广泛。根据手册:

TypeScript 强制派生类始终是其基类的子类型。

这意味着extends您覆盖的基类的类的成员是协变的(因为派生类始终是其基类的子类,或者简单地说,更具体)。考虑以下内容 - 覆盖有效,因为"A"它是更广泛的 union 的子类型"A" | "B"

class A {
    static b : Array<{ c: "A" | "B" }> = []
}

class B extends A {
    static b : Array<{ c: "A" }> = [] // OK
}

但是,相反的结果会导致可分配性错误,因为被覆盖的成员不是逆变的:

class C {
    static b : Array<{ c: "C" }> = []
}

class D extends C {
    static b : Array<{ c: "C" | "D" }> = [] // Type '"D"' is not assignable to type '"C"'
}

后一个示例在语义上等同于您的情况:eventName被声明为string literal type onKeyDown,这意味着不允许任何和所有扩展类扩展它,因此会出现错误。


您的选择是有限的,但是,有一种解决方法。假设您有以下基类E

class E {
    constructor(public e : string) {}
    static b : Array<{ c: "E" }> = []
    static s : number = 42;
}

首先,让我们声明派生类并以某种方式命名它,让它成为FB

class FB extends E {
    constructor(public f: number) {
        super(f.toString());
    }
}

到目前为止很简单,对吧?这是多汁的部分:

const F: Omit<typeof FB,"b"> & { 
    new (...args:ConstructorParameters<typeof FB>): InstanceType<typeof FB> 
    b: Array<{ c: "E" | "D" }>
} = FB;

有很多东西要解压。通过将声明的派生类分配给变量,我们创建了一个类表达式 const F = FB;,它使类的静态F部分能够通过变量的显式类型进行类型化。现在对于类型本身0

  • Omit<typeof FB, "b">确保编译器知道FB(因此,基类E)的静态部分存在,除了b我们稍后将重新定义的成员。
  • new (...args:ConstructorParameters<typeof FB>): InstanceType<typeof FB>提醒编译器这F是一个构造函数,而args:ConstructorParameters实用InstanceType程序让我们无需更新派生构造函数类型就可以更改基类。
  • b: ...将省略的b成员读入派生类的静态部分,同时扩展它(注意,由于不涉及类继承,所以没有错误)。

以上所有方法都在编译时修复了b成员,但我们仍然需要确保静态成员在运行时可用,如下所示(有关详细信息,请参阅 MDN on getOwnPropertyDescriptor/ defineProperty):

const descr = Object.getOwnPropertyDescriptor(E, "b")!;
Object.defineProperty(F, "b", { 
    ...descr, 
    value: [...descr.value, { c: "D" }] 
});

最后,让我们检查一切是否按预期工作:

console.log(
    new F(42), // FB
    new F(42).e, // string
    F.b, // { c: "E" | "D"; }[]
    F.s // number
);

// at runtime:
// FB: {
//   "e": "42",
//   "f": 42
// },
// "42",  
// [{ "c": "D" }],  
// 42 

上面有例子的游乐场| 适用于您的情况


0请注意,我们经常必须使用typeof FB类型查询——如果我们没有提前声明类并选择快捷方式到const F: { ... } = class ...,我们将无法在显式键入变量时引用类本身(如果我们尝试过,编译器会抱怨循环引用)。

于 2022-03-05T04:07:49.117 回答
0

它不快乐,因为您在这里说过它只能是onKeyDown

      eventName: "onKeyDown";

它可能应该只是string

      eventName: string;

如果您希望它始终以 开头on,您甚至可以这样做:

      eventName: `on${string}`;
于 2022-03-04T15:21:36.880 回答