为什么带有泛型范式(<T extended Parent>)的类的静态函数不能在子类中使用<T extended Child>

时间:2019-10-08 15:44:28

标签: typescript

类似的代码:

class A { 
    static foo<T extends A>() {
        return {} as T;
    }
}
class B extends A { 
    bar!:number
    static foo<T extends B>() {
        return {} as T;
    }
}

代码链接在其中:https://www.typescriptlang.org/play/?ssl=1&ssc=1&pln=11&pc=2#code/MYGwhgzhAECC0G9oFgBQ0PQgFzNglsNAGYD2pAPACrQCmAHtrQHYAmMsAfABQCUiaTEOgAnWtgCuI5ogC+0SNCoBuQZlloNqUJBgAhOoxbs4iFOkwAjMCICEALmYSAtpdoi1GHHkIly1QyY2fR5+BE9hMUlpOQUYFQitWSA

错误是:

Class static side 'typeof B' incorrectly extends base class static side 'typeof A'.
  Types of property 'foo' are incompatible.
    Type '<T extends B>() => T' is not assignable to type '<T extends A>() => T'.
      Type 'B' is not assignable to type 'T'.
        'B' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'A'.

1 个答案:

答案 0 :(得分:1)

无论好坏,TypeScript都会强制执行the static side of a subclass should be compatible with the static side of its superclass。这意味着您应该能够从子类中substitute用同名的属性/方法{超类的静态属性/方法而不会出现错误。 (好吧,除了构造函数签名外,有意免除此规则,以允许子类构造函数从其超类采用不同的参数)。 尚不清楚每个人是否都希望这样做(请参见relevant comment),但事实就是这样。

无论如何,这意味着如果您具有以下代码,

// some subtype of A that does not extend B
class C extends A {
    baz = 123;
}

let aFoo = A.foo;
aFoo<C>().baz; // number, okay

您必须能够用A.foo代替B.foo并使其仍然起作用:

let aFoo = B.foo;
aFoo<C>().baz; // error! C does not extend B

但事实并非如此。您的B.foo()定义不允许使用类型C的类型参数,因为C不扩展B。因此无法使用B.foo代替A.foo,因此B不能正确扩展A。这就是为什么您会得到一个错误。


不确定如何为您进行最佳处理。如果这是实例方法而不是静态方法,则建议您使用polymorphic this types表示“当前”类。但是有no such types available to static methods,所以这不是一个选择。

一种可行的解决方法是通过在运行时几乎没有帮助的辅助函数来显式扩展类的静态方面,并对其进行扩展:

function OmitStatic<T extends new (...args: any) => any, K extends keyof T>(
    ctor: T, ...k: K[]
): Omit<T, K> & (new (...args: ConstructorParameters<T>) => InstanceType<T>) {
    return ctor;
}

您将像这样使用它:

class A {
    static foo<T extends A>() {
        return {} as T;
    }
}

// note how we extend OmitStatic(A, "foo") instead of A
class B extends OmitStatic(A, "foo") {
    bar!: number
    static foo<T extends B>() {
        return {} as T;
    }
}

我们仍然具有实例端兼容性:

let b = new B();
let a = new A();
a = b; // okay, instance side is still substitutable

虽然静态端的行为符合您的要求,但B的定义内没有任何错误:

class C extends A {
    baz = 123;
}
A.foo<C>(); // okay
B.foo<C>(); // not okay

好的,希望能有所帮助;祝你好运!

Link to code