【问题标题】:Typescript bindings for static methods that hide a superclass method隐藏超类方法的静态方法的打字稿绑定
【发布时间】:2020-10-14 14:05:20
【问题描述】:

在 Javascript 中,类继承静态方法,但子类方法隐藏同名的超类方法,而不是重载它们。所以你可以做这样的事情:

class A {
    static foo(a) { ... }
}
class B extends A {
    static foo(a, b) { ... }
}

您可以调用 A.foo(a) 或 B.foo(a, b) ,它会按预期运行。不幸的是,Typescript 不喜欢这样,它似乎假设与实例方法相同的规则适用,即所有方法都是虚拟的。因此,尝试在 .d.ts 文件中表示上述内容,如下所示:

export class A {
    static foo(a: string): void
}
export class B extends A {
    static foo(a: string, b: number): void
}

导致错误,因为函数签名不兼容。我可以通过在BB 的声明中复制Afoo 声明来解决这个问题(.d.ts 文件也不需要与两者兼容的实现),但是这是一个谎言!我是说调用B.foo("") 是可以的,但实际上这无效并且可能导致未定义的行为。这是引入“type-danger”,与 Typescript 应该做的相反。有没有其他方法可以在 Typescript 中正确表示这些方法,或者至少像注释一样会在尝试调用不正确版本的代码中生成警告?这是 Typescript 中的错误吗?

【问题讨论】:

    标签: typescript overloading static-methods


    【解决方案1】:

    这不是 TypeScript 中的错误,而是有利于静态继承的设计决策。

    根据microsoft/TypeScript#4628 中的this comment,对于类的静态方面显然有两个不兼容的用例。一组用户希望看到 ES6 规范指定的静态继承,这意味着他们应该以与检查实例继承相同的方式进行检查:如果您不能将子类的静态属性替换为期望超类的静态属性,你做错了什么。

    另一组人不关心静态方面的可替代性。看起来您属于后者……这对您来说太糟糕了,因为 TypeScript 偏爱前者。如果您查看那个 GitHub 问题,看起来他们正在调查更改它,但它有点失败了。所以现在,就是这样。


    那么你能做什么(除了解决那个问题并给它一个?)?如果您只关心声明,例如在.d.ts 文件中,那么您可以执行标准库所做的事情并分别描述类的接口和静态方面,而无需使用class。 (例如,查看the Array class is typedArray<T>ArrayConstructor 的关系)。像这样:

    declare namespace MyModule {
      export interface A {
        // instance props/methods
      }
      export interface AConstructor {
        new(): A;
        foo(a: string): void;
      }
      export const A: AConstructor;
    
      export interface B extends A {
        // new pros/methods
      }
      export interface BConstructor {
        new(): B;
        foo(a: string, b: number): void;
      }
      export const B: BConstructor;
    }
    

    您可以测试它是否按预期工作。

    MyModule.A.foo("a"); // okay
    MyModule.B.foo("a"); // error
    

    另一方面,如果您关心具有行为的实际类定义,则可以编写一个类构造函数工厂函数,该函数在其类型中省略静态属性,以便编译器不会尝试强制执行静态端可替换性:

    function NoInheritStatics<A extends any[], R>(
      ctor: new (...a: A) => R
    ): new (...a: A) => R {
      return ctor;
    }
    

    然后像这样使用它:

    class A {
      a: string = "a";
      static foo(a: string) {
    
      }
    }
    
    class B extends NoInheritStatics(A) {
      b: string = "b";
      static foo(a: string, b: string) {
    
      }
    }
    

    这符合预期:

    const a = new A();
    a.a;
    A.foo("a");
    
    const b = new B();
    b.a;
    b.b;
    B.foo("a"); // error!
    

    请注意,这两种解决方案都没有真正改变运行时发生的任何事情;仍然存在静态继承。所有这些解决方案都是让 TypeScript 忽略任何此类继承。如果您需要支持非覆盖静态属性的继承(例如,如果A 有一个静态bar() 方法而B 没有,您是否希望能够编写B.bar()?)它可以做到但写出来会更复杂。无论如何,希望你现在有一些前进的道路。

    Playground link to code

    【讨论】:

    • 这些都是好主意,但我认为它们不能解决这个特殊问题。这些类需要直接扩展,即class MyClass extends LibClass。 FWIW 我正在研究 ts-for-gjs,其目的是让 Typescript 与 gjs 一起使用变得容易。 Javascript 实际上是支持 gobject-introspection 的 C 库的包装器。 GObject 的 API 非常适合 Typescript,但也有一些像这样令人头疼的地方。
    • 好的,那么带有相关 GitHub 问题链接的顶部构成了您问题的答案:这不是 TS 中的错误,并且可以解决它的请求功能还没有(还没有? ) 已实施。
    • 带接口的解决方案确实有效。我没有注意到 Typescript 将接口包含 new(...): A 的对象视为类/构造函数。事情变得更加复杂,因为 GObject 通常将构造函数实现为称为new 的静态函数,因此必须在真正的 JS 构造函数旁边声明这些构造函数。这可以使用语法new: (...) =&gt; A 来完成。
    猜你喜欢
    • 2011-05-04
    • 2022-11-25
    • 2017-09-11
    • 2011-11-17
    • 1970-01-01
    • 2020-09-11
    • 2017-08-20
    • 2021-04-27
    • 2021-08-13
    相关资源
    最近更新 更多