原文链接:Declaring Global Variables in TypeScript,2020.04.14,by Marius Schulz.
有时候,你可能想在 TypeScript 使用一些自定义全局变量。例如,在我的一些 Web 应用程序中,我需要将一些从服务器端渲染时传递过来的属性的给在浏览器中运行的 JavaScript 代码使用。为了做到这一点,通常我会在内联脚本中定义一个名为 __INITIAL_DATA__
的全局变量,并将一个 JSON 序列化对象赋值给它:
html
<script>
window.__INITIAL_DATA__ = {
userID: "536891193569405430",
};
</script>
现在,如果我尝试在一个 TypeScript 文件中访问 window.__INITIAL_DATA__
时,编译器就会产生一个类型错误,因为它找不到 __INITIAL_DATA__
属性的定义。
typescript
// Property '__INITIAL_DATA__' does not exist
// on type 'Window & typeof globalThis'
const initialData = window.__INITIAL_DATA__;
接下来,我将向你展示几种不同的方法,让 TypeScript 知道 window.__INITIAL_DATA__
属性的存在,并消除类型错误。
使用类型断言
消除类型错误的最快方法是在类型断言中使用 any
类型。我们可以将 window
对象视为任意类型,以便访问其 __INITIAL_DATA__
属性:
typescript
const initialData = (window as any).__INITIAL_DATA__;
这个解决方案有效,我们不再遇到类型错误。如果你需要一种临时的方式来访问 TypeScript 不知道的 window
对象上的属性,这是一个实用的方法。
(window as any).__INITIAL_DATA__
表达式是任意类型,因此 initialData
也是任意类型。我们可以进一步使用另一种类型断言来给 initialData
变量指定更具体的类型:
typescript
type InitialData = {
userID: string;
};
const initialData = (window as any).__INITIAL_DATA__ as InitialData;
现在,我们可以以类型安全的方式访问 initialData.userID
:
typescript
const userID = initialData.userID; // string 类型
请记住,这并不保证 window.__INITIAL_DATA__
在运行时会被正确设置。类型检查器相信我们,并且我们的工作是确保将一个符合预期形状的对象赋值给 window.__INITIAL_DATA__
。
声明一个全局变量
另一种方法是使用 declare var
语法声明一个全局变量。这样,我们可以让 TypeScript 找到这个具有给定名称和类型的全局变量:
typescript
declare var __INITIAL_DATA__: InitialData;
我们现在可以直接访问 __INITIAL_DATA__
变量了......
typescript
const initialData = __INITIAL_DATA__;
......或通过 window
对象获取:
typescript
const initialData = window.__INITIAL_DATA__;
有一点要注意的是,在ECMAScript 模块内部通过 declare var
声明的变量是是无法通过 window.__INITIAL_DATA__
形式访问的,会收到类型错误。而所谓 ECMAScript 模块就是包含顶级 import
或 export
声明的那些文件。
你可以使用 declare global { ... }
语法在全局作用域中声明一个全局变量,以便能够在 JavaScript 模块内部或外部文件通过 window.__INITIAL_DATA__
和 __INITIAL_DATA__
两种形式访问。
typescript
export function someExportedFunction() {
// ...
}
declare global {
var __INITIAL_DATA__: InitialData;
}
const initialData = window.__INITIAL_DATA__;
如果你需要在多个文件或模块中访问 window.__INITIAL_DATA__
,那么在项目中创建一个 globals.d.ts
文件可能是个好主意。在这个文件中,你可以声明所有将要使用的全局变量:
typescript
declare var __INITIAL_DATA__: InitialData;
增强 Window
接口
最后,你可以使用 TypeScript 的接口声明合并来告诉编译器在 Window 类型上有一个名为 __INITIAL_DATA__
的属性。为了做到这一点,你需要定义一个名为 Window
的接口,设置一个名为 __INITIAL_DATA__
的属性:
typescript
interface Window {
__INITIAL_DATA__: InitialData;
}
TypeScript 将会上面的这个接口定义和 lib.dom.d.ts
中定义的 Window
接口合并,从而得到一个单一的 Window
类型。这样一来,下面的赋值就不再产生类型错误了:
typescript
const initialData = window.__INITIAL_DATA__;
请注意,这种方法同样 ECMAScript 模块中不起作用。为了使 window.__INITIAL_DATA__
表达式能够正确进行类型检查,需要再次使用 declare global { ... }
语法:
typescript
export function someExportedFunction() {
// ...
}
declare global {
interface Window {
__INITIAL_DATA__: InitialData;
}
}
const initialData = window.__INITIAL_DATA__;