最好用一个例子来解释这一点。我需要在一个静态方法中引用当前类,这正如预期的那样工作:
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
。然而,即使this
是Cls
的子类,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
。
游乐场