在 Typescript 静态方法中使用 "this" 参数不会将类型缩小到当前 class?

Using "this" parameter in Typescript static methods doesn't narrow the type to the current class?

这最好用一个例子来解释。我需要在静态方法中引用当前 class,这按预期工作:

class Cls {
  static fn<T extends typeof Cls>(
    this: T,
    arg: T extends typeof Cls ? true : false,
  ) {}
}

Cls.fn(true);
Cls.fn(false); // Argument of type 'false' is not assignable to parameter of type 'true'.

但是,当从另一个静态方法调用 Cls.fn 时,它不起作用:

class Cls {
  static fn1<T extends typeof Cls>(
    this: T,
    arg: T extends typeof Cls ? true : false,
  ) {}

  static fn2<T extends typeof Cls>(this: T) {
    this.fn1(true); // Argument of type 'true' is not assignable to parameter of type 'T extends typeof Cls ? true : false'.
  }
}

TS Playground

有趣的是,如果我删除 fn2 的泛型或执行 this.fn1<typeof Cls>(true),它就会起作用。这意味着错误与 this 的类型有关。我认为这与 this 引用 Cls 的任何子 class 而不是恰好 Cls 有关。但是,即使 thisCls 的子 class,T extends typeof Cls 仍然为真。

这是 Typescript 使用错误值 this 的错误吗?如果这不是错误,我该如何修复它?

在我的实际代码中,我需要引用当前的 class,因为该方法根据子 class.

接受不同的参数

编辑: here's a more realistic example using subclasses

此行为由未解析的泛型类型参数解释。

这是由基本上是高阶函数(方法)引入的间接寻址引起的。在非泛型方法 fn2 的情况下(使用 Sub/Base 示例),当类型参数在派生 class Sub:

Base.fn<typeof Sub>(this: typeof Sub, arg: "sub"): void

不幸的是,在fn3的情况下,它本身就是一个泛型函数(方法),导致调用fn时的T泛型参数无法解析,这可以从推断的签名可以看出:

Base.fn<T>(this: T, arg: T extends typeof Sub ? "sub" : "base"): void

这澄清了以下编译器错误的含义 - 由于条件类型也未解决,因此 "sub""base" 都不能分配给 T extends typeof Sub ? "sub" : "base":

Argument of type '"sub"' is not assignable to parameter of type 'T extends typeof Sub ? "sub" : "base"'

正如您正确指出的那样,可以删除高阶方法的泛型类型参数,从而删除间接寻址:

static fn3(this: typeof Sub) {
    this.fn('base'); // error
    this.fn('sub'); // OK
}

然而,如果您需要 this:

,这会在进一步派生的 classes 中带来复杂性
class Sub {   
    static fn4(this: typeof Sub) {
        this.fn('sub');
        return this;
    } 
}

class SubSub extends Sub {}

SubSub.fn4(); // typeof Sub, probably wanted typeof SubSub

不过,还有一个替代方案 - 不要限制 T 参数,而是根据 T 的推断来限制 this 的类型。 classic 技术使用的是解析为 never:

自身的条件类型
class Sub extends Base {
  static fn3<T>(this: T extends typeof Sub? T: never) {
    this.fn('base'); // error
    this.fn('sub'); // OK
    return this;
  }
}

Sub.fn('sub'); // OK
Sub.fn('base'); // error
Sub.fn3(); // typeof Sub

内部 this.fn() 调用的签名推断为:

Base.fn<T extends typeof Sub ? T : never>(this: T extends typeof Sub ? T : never, arg: (T extends typeof Sub ? T : never) extends typeof Sub ? "sub" : "base"): void

通过进一步 延迟 通过 T extends typeof Sub ? T : never 的评估,我们实际上帮助了编译器:现在它保证 Ttypeof Sub到实例化时。

Playground