在TypeScript中,如何显式地在`window`上设置一个新属性?

1226

我通过在window对象上显式设置属性来为我的对象设置全局命名空间。

window.MyNamespace = window.MyNamespace || {};

TypeScript标出 MyNamespace 并提示:

属性“MyNamespace”在类型“window”的值上不存在任何内容"

我可以通过将 MyNamespace 声明为环境变量并删除 window 显式性来使代码工作,但我不想这样做。

declare var MyNamespace: any;

MyNamespace = MyNamespace || {};

如何保留window并使TypeScript不报错?

顺带一提,我发现很有趣的是TypeScript会抱怨,因为它告诉我windowany类型,而any类型可以包含任何东西。


一些答案是元问题的主题。 - Peter Mortensen
答案stackoverflow.com/a/47130953还附有一个链接到TypeScript文档的内容:从模块中扩展全局/模块范围 - kca
TypeScript的全局声明比你想象的要微妙得多。为了更好地理解,我建议你参考其他问题中的这些答案:在TypeScript中创建全局变量 在TypeScript中使用globalThis - undefined
30个回答

1447

我在另一个 Stack Overflow 的问题回答中找到了这个答案。

declare global {
    interface Window { MyNamespace: any; }
}

window.MyNamespace = window.MyNamespace || {};

基本上,您需要扩展现有的window接口,以告诉它关于您的新属性。


65
注意"Window"中的大写字母"W",这让我有点困惑。 - ajm
2
我无法使这个代码在tsc 1.0.1.0下编译通过。然而,Blake Mitchell的答案对我有效。 - Pat
71
declare global { interface Window { ... } } 在TypeScript 2.5.2中可用,无需像上述提到的那样使用.d.ts文件。 - tanguy_k
24
声明一个窗口接口,包含名为 "GLOBAL_VAR" 的任意类型变量。 - towry
32
如果你遇到错误信息 "TS2339: Property 'MyNamespace' does not exist on type 'Window & typeof globalThis'.",那么在文件开始处添加export {}即可解决问题。 - Mahdi Abdi
显示剩余9条评论

563

为保持动态,请使用:

(<any>window).MyNamespace

请注意,这可能无法与TSX一起使用,因为编译器可能会认为<any>是TSX元素。请参阅此答案以获取与TSX兼容的类型断言。


9
在 tsx 文件中如何实现这个想法? - velop
3
@martin说:“<any>”让它变得明确,所以我相信它可以正常工作。其他人说:如果你在使用Typescript与React或其他JSX环境(导致TSX语法),你将不得不使用“as”而不是“<>”。请参见@david-boyd的回答(链接在此处:https://dev59.com/aGcs5IYBdhLWcg3wmlSk)。 - Don
56
我需要使用 (window as any).MyNamespace。 - Arsalan Ahmad
1
我不得不在那一行上使用 /* tslint:disable */ 来禁用tslint。 - Michael Yagudaev
2
我认为采用这种方法不符合 TypeScript 的精神。当我使用 TS 编写我的项目时,我试图在用户发现错误之前提高自己发现错误的能力。这种方法仅仅是让类型检查器保持沉默,但并不能确保定义的成员被正确使用。 - Tess E. Duursma
显示剩余13条评论

307

使用 Svelte 或 TSX?其他答案对我都不起作用。

这是我所做的:

(window as any).MyNamespace

5
实际上与(<any> window).MyNamespace相同。 - Parzh from Ukraine
65
当使用TSX时,情况就不同了,因为<any>会被解释为JSX而不是类型转换。 - Jake Boone
1
感谢@David,这在新的Create React App和typescript版本中非常有效。以前这个是可以工作的:(<any>window).MyNamespace,但现在在新的typescript版本3.5.x中已经出现了问题。 - Jignesh Raval
11
窗口作为任何类型?那你为什么要使用TypeScript呢?直接用JavaScript就好啦 x) - MarcoLe
6
有趣的是人们使用了TypeScript,却对其保持沉默。这样使用与不使用几乎没有区别。而且想到这些答案是最受欢迎的!听起来很有前途…… - jperl
显示剩余2条评论

232

截至TypeScript ^3.4.3,此解决方案不再适用

或者...

你可以直接输入:

window['MyNamespace']

你不会收到编译错误,并且它的工作方式与键入 window.MyNamespace 相同。


24
但是你可能会遇到tslint错误... 如果你确实遇到了。 - smnbbrv
85
这完全违背了强类型的概念,也就是 TypeScript 背后的整个理念。 - d512
21
@user1334007 使用全局变量也可以实现相同的功能。但是,有些遗留代码需要使用它。 - nathancahill
7
不适用于^3.4.3版本。TypeScript错误:元素隐式具有“any”类型,因为类型“Window”没有索引签名。TS7017 - Green
10
我非常赞同@Auspex的观点。 TypeScript并不是你应该尝试规避的东西。如果你正在使用TS,请接受它并正确地定义类型。如果你不想这样做,就不要使用TypeScript。如果你被迫使用它但又不想,向经理说明换掉TS的原因,或者寻求帮助以正确获取类型。在TS代码库中进行被动攻击性的反弹以打乱代码,这种行为非常不专业且任性。 - Patrick
显示剩余6条评论

141

全局变量有些“邪恶” :) 我认为同时保持可移植性的最佳方法是:

首先,您导出接口:(例如,./custom.window.ts

export interface CustomWindow extends Window {
    customAttribute: any;
}

其次,您需要导入

import {CustomWindow} from './custom.window.ts';

第三步,使用CustomWindow将全局变量window进行转换:

declare let window: CustomWindow;

通过这种方式,如果您使用现有的window对象属性,那么在不同的IDE中也不会出现红线,因此最后尝试:

window.customAttribute = 'works';
window.location.href = '/works';

已测试通过 TypeScript 2.4.x 和最新版本!


5
您可能希望详细说明全局变量为何不好。在我们的情况下,我们正在window对象上使用一个非标准化的API,并进行了polyfill。这是否真的邪恶? - Mathijs Segers
4
@MathijsSegers,我不了解你的具体情况,但是关于全局变量的问题不仅存在于JavaScript中,在每种编程语言中都存在。其中一些原因包括:
  • 无法应用良好的设计模式
  • 存在内存和性能问题(这些问题在任何地方都存在,尝试将某些内容附加到String对象上并查看当您创建新的字符串时会发生什么)
  • 名称冲突导致代码产生副作用(特别是在具有异步特性的JavaScript中)
我可以继续讲下去,但我想你已经明白了...
- onalbi
4
@onabi,我实际上是在谈论浏览器的一个功能。由于它是浏览器,因此它在全球范围内都可用。您真的会建议仍然不应该采用全局定义吗?我的意思是,我们可以实现Ponyfills,但这将使支持该功能(例如全屏API)的浏览器变得臃肿。话虽如此,我并不认为所有全局变量都是邪恶的。 - Mathijs Segers
3
在我看来,应该尽可能避免使用或修改全局变量。虽然自从浏览器存在以来就有了window,但它也一直在不断变化。举个例子,如果我们现在定义window.feature='Feature',并且这在代码中被大量使用,那么如果浏览器在所有代码中添加了window.feature,这个功能就会被覆盖。总之,我的意思不是要反对您,谢谢。 - onalbi
2
全局变量并不是“邪恶”的;这样的东西有其存在的意义,否则它们就会从编程语言中被移除。有时候,全局变量确实是实现某些功能的最佳方式。邪恶的是“过度使用全局变量”。 - deltamind106
显示剩余5条评论

98

很简单:

文件 src/polyfills.ts

declare global {
  interface Window {
    myCustomFn: () => void;
  }
}

文件 my-custom-utils.ts

window.myCustomFn = function () {
  ...
};

如果您正在使用IntelliJ IDEA,在新的polyfills生效之前,您还需要更改IDE中的以下设置:

> File
> Settings
> Languages & Frameworks
> TypeScript
> check 'Use TypeScript Service'.

8
“声明全局”是关键,而这个答案并不特定适用于Angular CLI... - Avindra Goolcharan
是的,这是正确的。 - undefined
请注意,根据您的设置和声明文件,您可能需要使用declare interface Window而不带declare global包装器。 - undefined

83

被接受的答案是我曾经使用过的,但在 TypeScript 0.9.* 中它不再起作用了。 Window 接口的新定义似乎完全替换了内置定义,而不是增强了它。

我现在改为做这个:

interface MyWindow extends Window {
    myFunction(): void;
}

declare var window: MyWindow;

更新:使用 TypeScript 0.9.5 后,接受的答案再次可用。


2
这也适用于TypeScript 2.0.8使用的模块。示例:export default class MyClass{ foo(){ ... } ... }interface MyWindow extends Window{ mc: MyClass }declare var window: MyWindow window.mc = new MyClass()然后,您可以从Chrome Dev Tools控制台调用foo(),例如mc.foo() - Martin Majewski
如果您不想将某些内容声明为全局变量,那么这是一个非常好的答案。但另一方面,您需要在每个需要使用该变量的文件中调用“declare var...”。 - Puce
这种方法很好,因为在这种情况下,您不会与扩展monorepo中全局窗口的其他包发生冲突。 - Dmitrii Sorin
谢谢!在我看来,这是最佳答案。一个人不应该覆盖Window,因为它不是普通的窗口。仅仅绕过类型检查或试图欺骗TS也不是解决问题的方法。 - Rutger Willems

69

如果您需要使用import来扩展window对象以支持自定义类型,可以使用以下方法:

window.d.ts

import MyInterface from './MyInterface';

declare global {
    interface Window {
        propName: MyInterface
    }
}

请参见手册中“声明合并”部分的“全局扩充”。


55
创建名为global.d.ts的文件,例如/src/@types/global.d.ts,然后定义一个接口,如下所示:
interface Window {
  myLib: any
}

参考文献: 全局 .d.ts


49

大多数其他答案并不完美。

  • 有些只是为了展示而抑制类型推断。
  • 有些只关心全局变量作为命名空间,而不是作为接口/类

今天早上我也遇到了类似的问题。我在 Stack Overflow 上尝试了很多“解决方案”,但没有一个能够完全不产生类型错误并启用 IDE (WebStormVisual Studio Code) 中的类型跳转。

最终,从 Allow module definitions to be declared as global variables #3180

我找到了一个合理的解决方法,可以为一个既充当接口/类又充当命名空间的全局变量添加类型。

以下是示例:

// typings.d.ts
declare interface Window {
    myNamespace?: MyNamespace & typeof MyNamespace
}

declare interface MyNamespace {
    somemethod?()
}

declare namespace MyNamespace {
    // ...
}

上面的代码将命名空间MyNamespace和接口MyNamespace的类型合并到全局变量myNamespacewindow的属性)中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接