Typescript 中的 XHR 拦截器

XHR Interceptor in Typescript

为什么 Typescript 中不允许使用以下猴子补丁?

const oldXHROpen = window.XMLHttpRequest.prototype.open

window.XMLHttpRequest.prototype.open = function (
    method: string,
    url: string
): void {
    return oldXHROpen.apply(this, [method, url])
}

它给出了以下错误:

Argument of type '[string, string]' is not assignable to parameter of type '[method: string, url: string | URL, async: boolean, username?: string | null | undefined, password?: string | null | undefined]'.
  Source has 2 element(s) but target requires 3.

然而,在查看 open 的定义时,有一种方法只需要两个参数。

open(method: string, url: string | URL): void;

这是 TypeScript 的设计限制;有关详细信息,请参阅 microsoft/TypeScript#38353

TypeScript standard library's typings for the XHMLHttpRequest.open() method 看起来像这样:

interface XMLHttpRequest {
    open(method: string, url: string | URL): void;
    open(method: string, url: string | URL, async: 
      boolean, username?: string | null, password?: string | null): void;
}

有两个调用签名,这使得 open() 成为 TypeScript 中的 overloaded 方法。如果直接调用重载的方法或函数,编译器会select根据你传入的参数重载其中之一

但它不能纯粹在类型级别上做到这一点。如果您开始以编程方式操作重载函数类型的类型,编译器将立即选择一个调用签名,而不会尝试找出最适合这种情况的调用签名。通常这是最后一个调用签名。

在这种情况下,假设您 the --strictBindCallApply compiler option enabled, the apply() method for CallableFunction 只看到 XHMLHttpRequest.open() 的第二个调用签名。该调用签名至少需要三个参数,因此当您 apply() 其中两个参数时编译器会生气。哎呀


这种情况下的解决方法是手动将 oldXHROpen 的类型从一对调用签名扩展为您尝试支持的单个调用签名。由于扩大是一项安全操作,您可以使用 type annotation 来执行此操作并且不会放弃任何类型安全性:

const oldXHROpen: (method: string, url: string | URL) => void =
  window.XMLHttpRequest.prototype.open // okay

一旦你这样做,apply() 方法只会看到你关心的 open() 调用签名,一切正常:

window.XMLHttpRequest.prototype.open = function (
  method: string,
  url: string
): void {
  return oldXHROpen.apply(this, [method, url])  // okay
}

Playground link to code