【问题标题】:Typing of overridden method, called by inherited method, in TypeScript (Bug?)在 TypeScript 中键入由继承方法调用的重写方法(错误?)
【发布时间】:2021-02-18 00:01:51
【问题描述】:

我最近偶然发现了以下场景:

class A {
  public m1(x: string | string[]): string | string[] {
    return this.m2(x);
  }
  protected m2(x: string | string[]): string | string[] {
    return x;
  }
}
class B extends A {
  protected m2(x: string[]): string { // no compiler warning
    return x.join(',');
  }
}
const b = new B();
console.log(b.m1(['a', 'b', 'c'])); // ok
console.log(b.m1('d')); // runtime error

这是 TypeScript 打字系统中的错误还是故意的?如果是后者,如何更改类型以便编译器识别问题?

【问题讨论】:

    标签: typescript inheritance methods overriding


    【解决方案1】:

    这是预期的一种行为。在 typescript 中,你重写一个方法来集中该方法的特殊性,而不是像在其他语言中那样扩大它。

    至于解释……

    class A {
      public m1(x: string | string[]): string | string[] {
        /**
         * Here variable 'x' is of type string | string[], which perfectly overlaps 
         * with the arguments for the method m2. So you won't get a compile-time error
         */
        return this.m2(x);
      }
      protected m2(x: string | string[]): string | string[] {
        return x;
      }
    }
    class B extends A {
      /**
       * Here method m2 is overridden by a method which accepts a string[], which
       * is covered by the parent's method, and hence no compile-time error. If
       * you made the argument type to 'string', it would still be accepted.
       */
      protected m2(x: string[]): string { // no compiler warning
        return x.join(',');
      }
    }
    const b = new B();
    console.log(b.m1(['a', 'b', 'c'])); // ok
    /**
     * Here you are passing the type you have specified method m1 will take. So there is no issue here as well
     */
    console.log(b.m1('d')); // runtime error
    

    一切都说了,这是一个逻辑错误,而不是一个错误。

    【讨论】:

    • 谢谢。但是在我看来,根据OOP继承,B.m2不应该被允许这种方式,因为B的实例不能做某事(即,接受一个string为m2)那个父类的实例A 可以。请注意,当我调用b.m2("somestring") 时,仍然会调用B.m2,这会导致错误,而不是A.m2(这可能是合理的)。
    【解决方案2】:

    在 TypeScript 中,方法是 bivariant,函数属性是与 --strictFunctionTypes 逆变的。注意它们的不同语法:

    class MyClass {
      public fn(a: string): string { ... } // this is a method
      public fn = (a: string): string => { ... } // this is a function as property
    }
    

    要为m2 启用更严格的输入,您需要启用--strictFunctionTypes(默认为strict)并使用函数属性:

    protected m2 = (x: string | string[]): string | string[] => {
      return x;
    }
    

    现在,m2 会正确出错,您需要区分 string 和 string[]:

    protected m2 = (x: string | string[]): string => { 
      return Array.isArray(x) ? x.join(',') : `${x},`;
    }
    

    Live code example on Playground

    相关:What are covariance and contravariance?

    相关:JS class fields

    【讨论】:

    • 谢谢,我明白了。我从没想过在方差方面方法和函数属性之间存在差异。此外,正如您的第一个相关链接所述,“在 TypeScript 中,参数类型是双变量的(协变和逆变),这是不合理的”。
    • 是的,方法被认为是不健全的。这是核心开发人员有意识地决定使用内置方法(例如来自Array),而不是像大多数用例所必需的那样复杂。
    猜你喜欢
    • 2015-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-05
    • 2011-02-20
    相关资源
    最近更新 更多