简体   繁体   English

在 Typescript static 方法中使用“this”参数不会将类型缩小到当前的 class?

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

This is best explained using an example.这最好用一个例子来解释。 I need to reference the current class in a static method, this works as expected:我需要在 static 方法中引用当前的 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'.

However, when calling Cls.fn from another static method, it doesn't work:但是,当从另一个 static 方法调用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 TS游乐场

Interestingly, it works if I remove fn2 's generic or do this.fn1<typeof Cls>(true) .有趣的是,如果我删除fn2的泛型或执行this.fn1<typeof Cls>(true) ,它会起作用。 This means the error is related to the type of this .这意味着错误与this的类型有关。 I think it has to do with this referring to any subclass of Cls as opposed to exactly Cls .我认为this与指代Cls的任何子类有关,而不是确切的Cls However, even if this is a subclass of Cls , T extends typeof Cls would still be true.但是,即使thisCls的子类, T extends typeof Cls仍然是正确的。

Is this a bug where Typescript uses the wrong value for this ? this是 Typescript 使用错误值的错误吗? If it's not a bug, how can I fix it?如果它不是一个错误,我该如何修复它?

In my actual code, I need to reference the current class because the method accepts different arguments depending on the subclass.在我的实际代码中,我需要引用当前的 class,因为该方法根据子类接受不同的 arguments。

Edit: here's a more realistic example using subclasses编辑: 这是一个使用子类的更现实的例子

The behavior is explained by an unresolved generic type parameter.该行为由未解析的泛型类型参数解释。

This is caused by an indirection introduced by what is basically a higher-order function (method).这是由基本上是高阶 function(方法)引入的间接引起的。 In the case of a non-generic method fn2 (using the Sub / Base example), the inference works as expected as the type parameter gets resolved to typeof Sub in the derived class Sub :在非泛型方法fn2 (使用Sub / Base示例)的情况下,推理按预期工作,因为 type 参数在派生的 class Sub中解析为typeof Sub Sub :

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

Unfortunately, in the case of fn3 , it is itself a generic function (method), leading to the T generic parameter in calls to fn being unresolved, which can be seen from the inferred signature:不幸的是,在fn3的情况下,它本身就是一个泛型 function (方法),导致对fn的调用中的T泛型参数无法解析,这可以从推断的签名中看出:

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

This clears up what the following compiler error means - as the conditional type is also left unresolved, neither "sub" nor "base" will be assignable to T extends typeof Sub? "sub": "base"这清除了以下编译器错误的含义 - 由于条件类型也未解决, "sub""base"都不能分配给T extends typeof Sub? "sub": "base" T extends typeof Sub? "sub": "base" : T extends typeof Sub? "sub": "base"

Argument of type '"sub"' is not assignable to parameter of type 'T extends typeof Sub? '"sub"' 类型的参数不能分配给 'T 扩展 typeof Sub 类型的参数? "sub": "base"' “子”:“基地”'

One could, as you rightfully noted, remove the generic type parameter of the higher-order method, thus removing the indirection:正如您正确指出的那样,可以删除高阶方法的泛型类型参数,从而删除间接:

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

This, however, presents complications in further derived classes should you ever need this :但是,如果您需要, 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

There is an alternative, though - do not constrain the T parameter and instead constrain the type of this based on what T is inferred to be.不过,还有另一种选择 - 不要限制T参数,而是根据推断的T来限制this的类型。 The classic technique is using a conditional type that resolves to itself of never :经典技术是使用解析为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

The signature of the inner this.fn() call is inferred as:内部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

By further deferring the evaluation via T extends typeof Sub? T: never通过进一步推迟评估T extends typeof Sub? T: never T extends typeof Sub? T: never we actually helped the compiler: now it nows that T is guaranteed to be typeof Sub by the time of instantiation. T extends typeof Sub? T: never真正帮助过编译器:现在T在实例化时保证是typeof Sub

Playground 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM