打字稿错误:类型...的“this”上下文无法分配给方法的类型“this”

Sve*_*ven 11 typescript

我目前正在调试一些库的代码,并且我一生都不理解特定的错误。

这是我正在调试的代码的简化示例:

type Test1Type<V> = object | ((this: V) => object);

function testFnc(x: string, test1Fnc: Test1Type<string>) {
  let res;

  if (typeof test1Fnc === "function") {
    res = test1Fnc.call(x, x);
  }
}
Run Code Online (Sandbox Code Playgroud)

在该行中,res = test1Fnc.call(x, x);该术语test1Fnc被标记为错误:

(parameter) test1Fnc: Function | ((this: string) => object)

The 'this' context of type 'Function | ((this: string) => object)' is not assignable to method's 'this' of type '((this: string) => object) & Function'.
  Type 'Function' is not assignable to type '((this: string) => object) & Function'.
    Type 'Function' is not assignable to type '(this: string) => object'.
      Type 'Function' provides no match for the signature '(this: string): object'.ts(2684)
Run Code Online (Sandbox Code Playgroud)

的内部类型定义.call()似乎是这样的:

call<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A): R;

不幸的是,我只是无法弄清楚这段代码出了什么问题,特别是因为我正在调试的库直接在其代码库中包含了这段代码(请参阅 参考资料)。

我真的不明白错误消息在这里告诉我什么。

我正在使用"typescript": "~3.9.3"(图书馆正在使用"typescript": "^2.8.3")。

感谢任何帮助,因为我真的很想了解这里发生的事情。

Sim*_*obs 13

Function您发现 TypeScript 处理联合类型交互的方式不一致。

编译器做什么

call调用时,编译器知道test1Fnc是:

  1. 某个未知签名的JavaScriptFunction对象,或者
  2. Function一个在字符串上调用的JavaScript对象(即它有一个字符串作为this参数)并返回一个对象。

然后,编译器会询问该调用对于每种类型是否有效。

当它询问该调用对于类型 1 是否有效时,它会得出结论该调用无效,因为test1Fnc可能无法在字符串上调用。

为什么这很奇怪

这很奇怪,因为这与对象的常规 TypeScript 行为相反Function,它通常允许任何类型的函数调用。要查看是否允许此类调用,请暂时将初始类型声明更改为type Test1Type<V> = object,错误就会消失。

发生这种情况是因为有关Function编译器如何处理类型的两件事是正确的:

  1. 对它们进行任何类型的函数调用都是允许的,但是
  2. 它们不可分配给具有指定参数的函数。

当编译器知道test1Fncis a Functiononly 时(如类型声明更改时),它会根据规则 1 起作用。但是当涉及联合类型时,它首先尝试将联合类型分配给该call类型的给定签名。由于规则 2 在规则 1 有机会覆盖它之前,该分配就失败了。

为什么这不是编译器应有的行为

您可以看到这不是预期的行为,因为 ifTest1Type<V>被定义为这两种类型中的一种(假设进行了下一节中解释的修改),那么编译器不会给出任何错误。这是不对的:如果程序的路径对于联合类型中的任一类型都有效,则该程序应该对联合类型有效。

但无论如何类型都是错误的

作为另一个观察,即使test1Fnc是上面的类型 2,它也不会无法用call所列出的函数来调用。给定call函数需要一个thisstring以及一个类型 的常规参数string,但上面的类型 2 只是一个没有常规参数this的类型值。string编译器目前没有显示此错误,但如果将初始类型声明更改为type Test1Type<V> = ((this: V) => object)then 您将看到产生不同的错误。反过来,通过将行更改为 可以消除此问题type Test1Type<V> = ((this: V, arg: V) => object)

建议:重新定义、强制转换、报告并且不要使用联合

为了解决这个问题,您可以考虑以下一项或多项:

  1. Redefine Test1Type<V> = object | ((this: V, arg: V) => object),这似乎是代码所期望的。这并没有真正改变类型,因为它仍然可以是任何对象,但它确实更好地表达了代码的意图
  2. test1Fnc然后,如果您可以将块内的类型强制转换if为预期类型,则可以清除错误:res = (test1Fnc as (this: string, arg: string) => object).call(x,x)。如果您无法进行这样的更改,您可能不得不放弃任何类型安全并转向type Test1Type = object
  3. 您可能想在 TypeScript GitHub 上报告此问题。我不确定这对他们来说是一个优先事项,因为Function可能不鼓励使用对象进行打字,并且
  4. 无论如何,我不鼓励使用这种联合类型。它太笼统了,无论如何都没有真正实现类型安全。目前尚不清楚您对代码有多少控制权,以及Test1Type<V>可能需要哪些其他值,但如果您可以进行更广泛的更改,最好将其编写为更显式类型的联合,以便编译器可以在它们之间进行选择,或更严格地以完全 OOP 风格建立类层次结构。