在几乎所有的前端应用中,你都可能遇到过这个错误:
类型'Window & typeof globalThis'上不存在属性'X'。
在这篇文章中,我们将介绍几个不同的解决方案来解决这个问题。
快速修复
Window
接口在名为 lib.dom.d.ts
的文件中全局定义,可以使用各种技术来更改它:
-
若要在
.ts
或.tsx
文件中全局更改它,可以使用declare global
和interface Window
:
-
如果要在
.d.ts
文件中全局更改它,可以指定interface Window
。
-
要只对一个文件进行更改,可以在
.ts
或.tsx
文件中使用declare const window
:
解释
接口 Window
在 TypeScript 的全局作用域中,作为 lib.dom.d.ts
中的 DOM 类型的一部分,它描述了浏览器中可用的方法。
当第三方脚本(或者我们自己的代码)向窗口添加一些内容时,问题就出现了:
window.X = Date.now();
这是一个问题,因为 TypeScript 不知道 X
属性,所以当你试图访问它时,它会抛出一个错误:
// @errors: 2339
window.X;
因此,我们需要以某种方式改变 Window
的全局定义,以便包含 TypeScript 不知道的新属性。
解决方案1: declare global
在 .ts
或 .tsx
文件中
第一个解决方案是使用 declare global
来改变 Window
的全局定义:
这样做有两个原因:首先, declare global
告诉 TypeScript 它里面的任何内容都应该添加到全局作用域中。
第二,我们利用了声明合并,这是 TypeScript 中的一个规则,在相同的作用域中,同名的接口会被合并,所以通过在相同的作用域中重新声明 Window
,我们可以为它附加新的属性。
如果我们使用 type
,这将不起作用,因为类型不支持声明合并。
// @errors: 2300
export {};
// ---cut---
declare global {
type Window = {
X: number;
};
}
这个解决方案只在 .ts
或 .tsx
文件中起作用,因为 declare global
只在模块中起作用。因此,就您将定义放在项目中的什么位置而言,这个解决方案有点笨拙。在自己的模块中?与其他东西共存?
解决方案 2:一个 .d.ts
文件
一种更整洁的解决方案是在 .d.ts
文件中使用 interface Window
:
之所以这样做是因为你放在 .d.ts
中的任何内容都会自动进入全局作用域,所以不需要 declare global
。
它还允许您将全局定义放在单独的文件中,这比试图找出应该把它放到哪个 .ts
文件中感觉更干净一些。
解决方案3:单模块覆盖
如果你担心在全局作用域中添加类型,你可以使用 declare const window
来重写单个模块中的 window
的类型:
export {};
// ---cut---
declare const window: {
X: number;
} & Window;
window.X;
// ^?
declare const window
的作用类似于 const window
,但是在类型级别上,所以它只作用于当前作用域 — 在本例中,是模块,我们声明的类型是当前的 Window
加上新的属性 X
。
如果你需要访问多个文件中的 window.X
,这个解决方案会有点烦人,因为你需要在每个文件中添加 declare const window
行。
我应该使用哪种解决方案?
我个人倾向于使用解决方案 2 — 一个 .d.ts
文件,它只有最少的代码行,并且最容易放置在项目中。
我不介意在全局作用域中添加类型。通过实际更改 Window
的类型,您可以更准确地描述代码执行的环境。在我看来,这是额外的好处。
但是如果你真的担心,可以使用 declare const
解决方案。
欢迎关注公众号:文本魔术,了解更多