likes
comments
collection
share

如何在 TypeScript 中声明全局变量?

作者站长头像
站长
· 阅读数 9

原文链接:Declaring Global Variables in TypeScript,2020.04.14,by Marius Schulz.

有时候,你可能想在 TypeScript 使用一些自定义全局变量。例如,在我的一些 Web 应用程序中,我需要将一些从服务器端渲染时传递过来的属性给在浏览器中运行的 JavaScript 代码使用。为了做到这一点,通常我会在内联脚本中定义一个名为 __INITIAL_DATA__ 的全局变量,并将一个 JSON 序列化对象赋值给它:

<script>
  window.__INITIAL_DATA__ = {
    userID: "536891193569405430",
  };
</script>

现在,如果我尝试在一个 TypeScript 文件中访问 window.__INITIAL_DATA__ 时,编译器就会产生一个类型错误,因为它找不到 __INITIAL_DATA__ 属性的定义。

// Property '__INITIAL_DATA__' does not exist
// on type 'Window & typeof globalThis'
const initialData = window.__INITIAL_DATA__;

接下来,我将向你展示几种不同的方法,让 TypeScript 知道 window.__INITIAL_DATA__ 属性的存在,并消除类型错误。

使用类型断言

消除类型错误的最快方法是在类型断言中使用 any 类型。我们可以将 window 对象视为任意类型,以便访问其 __INITIAL_DATA__ 属性:

const initialData = (window as any).__INITIAL_DATA__;

这个解决方案有效,我们不再遇到类型错误。如果你需要一种临时的方式来访问 TypeScript 不知道的 window 对象上的属性,这是一个实用的方法。

(window as any).__INITIAL_DATA__ 表达式是任意类型,因此 initialData 也是任意类型。我们可以进一步使用另一种类型断言来给 initialData 变量指定更具体的类型:

type InitialData = {
  userID: string;
};

const initialData = (window as any).__INITIAL_DATA__ as InitialData;

现在,我们可以以类型安全的方式访问 initialData.userID

const userID = initialData.userID; // string 类型

请记住,这并不保证 window.__INITIAL_DATA__ 在运行时会被正确设置。类型检查器相信我们,并且我们的工作是确保将一个符合预期形状的对象赋值给 window.__INITIAL_DATA__

声明一个全局变量

另一种方法是使用 declare var 语法声明一个全局变量。这样,我们可以让 TypeScript 找到这个具有给定名称和类型的全局变量:

declare var __INITIAL_DATA__: InitialData;

我们现在可以直接访问 __INITIAL_DATA__ 变量了……

const initialData = __INITIAL_DATA__;

……或通过 window 对象获取:

const initialData = window.__INITIAL_DATA__;

有一点要注意的是,在 ECMAScript 模块内部通过 declare var 声明的变量是是无法通过 window.__INITIAL_DATA__ 形式访问的,会收到类型错误。而所谓 ECMAScript 模块就是包含顶级 importexport 声明的那些文件。

你可以使用 declare global { ... } 语法在全局作用域中声明一个全局变量,以便能够在 JavaScript 模块内部或外部文件通过 window.__INITIAL_DATA____INITIAL_DATA__ 两种形式访问。

export function someExportedFunction() {
  // ...
}

declare global {
  var __INITIAL_DATA__: InitialData;
}

const initialData = window.__INITIAL_DATA__;

如果你需要在多个文件或模块中访问 window.__INITIAL_DATA__,那么在项目中创建一个 globals.d.ts 文件可能是个好主意。在这个文件中,你可以声明所有将要使用的全局变量:

declare var __INITIAL_DATA__: InitialData;

增强 Window 接口

最后,你可以使用 TypeScript 的接口声明合并来告诉编译器在 Window 类型上有一个名为 __INITIAL_DATA__ 的属性。为了做到这一点,你需要定义一个名为 Window 的接口,设置一个名为 __INITIAL_DATA__ 的属性:

interface Window {
  __INITIAL_DATA__: InitialData;
}

TypeScript 将会上面的这个接口定义和 lib.dom.d.ts 中定义的 Window 接口合并,从而得到一个单一的 Window 类型。这样一来,下面的赋值就不再产生类型错误了:

const initialData = window.__INITIAL_DATA__;

请注意,这种方法同样 ECMAScript 模块中不起作用。为了使 window.__INITIAL_DATA__ 表达式能够正确进行类型检查,需要再次使用 declare global { ... } 语法:

export function someExportedFunction() {
  // ...
}

declare global {
  interface Window {
    __INITIAL_DATA__: InitialData;
  }
}

const initialData = window.__INITIAL_DATA__;