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



最好用一个例子来解释这一点。我需要在一个静态方法中引用当前类,这正如预期的那样工作:

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游乐场

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

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

在我的实际代码中,我需要引用当前类,因为该方法根据子类接受不同的参数。

编辑:这里有一个使用子类的更现实的例子

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

这是由基本上是高阶函数(方法(引入的间接性引起的。在非泛型方法fn2的情况下(使用Sub/Base示例(,当类型参数被解析为派生类Sub:中的typeof 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":

类型为"的参数;子"不可分配给类型为"T"的参数扩展Sub?的类型"sub":"基">

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

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

然而,如果您需要this:,这会在进一步派生的类中带来复杂性

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的类型。经典技术是使用一个解析为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的评估,我们实际上帮助了编译器:现在T在实例化时保证是typeof Sub

游乐场

最新更新