lib.d.ts

当你安装 TypeScript 时,会顺带安装 lib.d.ts 等声明文件。此文件包含了 JavaScript 运行时以及 DOM 中存在各种常见的环境声明。

  • 它自动包含在 TypeScript 项目的编译上下文中;
  • 它能让你快速开始书写经过类型检查的 JavaScript 代码。
    你可以通过指定 —noLib 的编译器命令行标志(或者在 tsconfig.json 中指定选项 noLib: true)从上下文中排除此文件。

使用例子

看如下例子:

  1. const foo = 123;
  2. const bar = foo.toString();

这段代码的类型检查正常,因为 lib.d.ts 为所有 JavaScript 对象定义了 toString 方法。

如果你在 noLib 选项下,使用相同的代码,这将会出现类型检查错误:

  1. const foo = 123;
  2. const bar = foo.toString(); // Error: 属性 toString 不存在类型 number 上

现在你已经理解了 lib.d.ts 的重要性,至于它的内容是怎么样的,我们接下来解释。

观察 lib.d.ts 的内部

lib.d.ts 的内容主要是一些变量声明(如:windowdocumentmath)和一些类似的接口声明(如:WindowDocumentMath)。

最简单的方式寻找代码的类型(如:Math.floor)是使用 IDE 的 F12(跳转到定义)。

让我们来看一个示例变量的声明,如 window 被定义为:

  1. declare var window: Window;

这只是一个简单的 declare var,后面跟一个变量名称(window)和一个用来类型注解的接口(Window 接口),这些变量通常指向一些全局的接口,例如,以下是 Window 接口的一小部分:

  1. interface Window extends EventTarget, WindowTimers, WindowSessionStorage, WindowLocalStorage, WindowConsole, GlobalEventHandlers, IDBEnvironment, WindowBase64 {
  2. animationStartTime: number;
  3. applicationCache: ApplicationCache;
  4. clientInformation: Navigator;
  5. closed: boolean;
  6. crypto: Crypto;
  7. // so on and so forth...
  8. }

你可以在这些接口里看到大量的类型信息,当你不使用 TypeScript 时,你需要将它们保存在你的大脑里。现在你可以使用 intellisense 之类东西,从而可以减少对知识的记忆。

使用这些全局变量是有利的。在不更改 lib.d.ts 的情景下,它可以让你添加额外的属性。接下来,我们将介绍这些概念。

修改原始类型

在 TypeScript 中,接口是开放式的,这意味着当你想使用不存在的成员时,你仅仅是需要添加它们至 lib.d.ts 中的接口声明中,TypeScript 将会自动接收它。注意,你需要在全局模块中做这些修改,以使这些接口与 lib.d.ts 相关联。我们推荐你创建一个称为 globals.d.ts 的特殊文件。

这里有我们需要添加至 WindowMathDate 的一些例子:

Window

仅仅是添加至 Window 接口:

  1. interface Window {
  2. helloWorld(): void;
  3. }

这将允许你以一种类型安全的形式使用它:

  1. // Add it at runtime
  2. window.helloWorld = () => console.log('hello world');
  3. // Call it
  4. window.helloWorld();
  5. // 滥用会导致错误
  6. window.helloWorld('gracius'); // Error: 提供的参数与目标不匹配

Math

全局变量 Mathlib.d.ts 中被定义为:

  1. /** An intrinsic object that provides basic mathematics functionality and constants. */
  2. declare var Math: Math;

即变量 MathMath 的一个实例,Math 接口被定义为

  1. interface Math {
  2. E: number;
  3. LN10: number;
  4. // others ...
  5. }

当你想在 Math 全局变量上添加你需要的属性时,你仅需要把它添加至 Math 的全局接口上即可,例如:在seedrandom Project项目里,它添加了 seedrandom 函数至全局的 Math 对象上,这可以很容易的被声明:

  1. interface Math {
  2. seedrandom(seed?: string): void;
  3. }

你可以像下面一样使用它:

  1. Math.seedrandom();
  2. Math.seedrandom('Any string you want');

Date

如果你在 lib.d.ts 中寻找 Date 定义的声明,你将会找到:

  1. declare var Date: DateConstructor;

接口 DateConstructor 与你在上文中看到的 MathWindow 接口一样,因为它涵盖了可以使用的 Date 全局变量的成员(如:Date.now())。除此之外,它还包含了可以让你创建 Date 实例的构造函数签名(如:new Date())。DateConstructor 接口的一部分代码如下所示:

  1. interface DateConstructor {
  2. new (): Date;
  3. // 一些其他的构造函数签名
  4. now(): number;
  5. // 其他成员函数
  6. }

dayjs 里,它在 Date 的全局变量以及 Date 实例上同时添加了成员,因此这个库的 TypeScript 定义看起来像如下所示(社区已经定义好了):

  1. // DateJS 公开的静态方法
  2. interface DateConstructor {
  3. /** Gets a date that is set to the current date. The time is set to the start of the day (00:00 or 12:00 AM) */
  4. today(): Date;
  5. // ... so on and so forth
  6. }
  7. // DateJS 公开的实例方法
  8. interface Date {
  9. /** Adds the specified number of milliseconds to this instance. */
  10. addMilliseconds(milliseconds: number): Date;
  11. // ... so on and so forth
  12. }

这允许你在类型安全的情况下做:

  1. const today = Date.today();
  2. const todayAfter1second = today.addMilliseconds(1000);

string

如果你在 lib.d.ts 里寻找 string,你将会找到与 Date 相类似的内容(全局变量 StringStringConstructor 接口,String 接口)。但是值得注意的是,String 接口也会影响字符串字面量,如下所示:

  1. interface String {
  2. endsWith(suffix: string): boolean;
  3. }
  4. String.prototype.endsWith = function(suffix: string): boolean {
  5. const str: string = this;
  6. return str && str.indexOf(suffix, str.length - suffix.length) !== -1;
  7. };
  8. console.log('foo bar'.endsWith('bas')); // false
  9. console.log('foo bas'.endsWith('bas')); // true

终极 string

基于可维护性,我们推荐创建一个 global.d.ts 文件。然而,如果你愿意,你可以通过使用 declare global { / global namespace / },从文件模块中进入全局命名空间:

  1. // 确保是模块
  2. export {};
  3. declare global {
  4. interface String {
  5. endsWith(suffix: string): boolean;
  6. }
  7. }
  8. String.prototype.endsWith = function(suffix: string): boolean {
  9. const str: string = this;
  10. return str && str.indexOf(suffix, str.length - suffix.length) !== -1;
  11. };
  12. console.log('foo bar'.endsWith('bas')); // false
  13. console.log('foo bas'.endsWith('bas')); // true

使用你自己定义的 lib.d.ts

如上文说提及,使用 —noLib 编译选项会导致 TypeScript 排除自动包含的 lib.d.ts 文件。为什么这个功能是有效的,我例举了一些常见原因:

  • 运行的 JavaScript 环境与基于标准浏览器运行时环境有很大不同;
  • 您希望在代码里严格的控制全局变量,例如:lib.d.ts 定义了 item 作为全局变量,你并不希望它泄漏到你的代码里。
    一旦你排除了默认的 lib.d.ts 文件,你可以在编译上下文中包含一个类似命名的文件,TypeScript 将选择它进行类型检查。

TIP

小心使用 —noLib 选项,一旦你使用使用了它,当你把你的项目分享给其他人时,它们也将被迫使用 —noLib 选项,更糟糕的是,如果将这些代码放入你的项目中,你可能需要将它放入你的基于 lib 代码中。

编译目标对 lib.d.ts 的影响

设置编译目标为 es6 时,能导致 lib.d.ts 包含更多的像 Promise 的现代(es6)内容的环境声明。编译器目标的这种神奇作用,改变了代码的环境,这对某些人来说是理想的,但是这对另外一些人来说造成了困扰,因为它将编译出的代码与环境混为一谈。

当你想对环境进行更细粒的控制时,你应该使用我们接下来将要讨论的 —lib 选项。

—lib 选项

一些时候,你想要解耦编译目标(生成的 JavaScript 版本)和环境库支持之间的关系。例如对于 Promise,你的编译目标是 —target es5,但是你仍然想使用它,这个时候,你可以使用 lib 对它进行控制。

TIP

使用 —lib 选项可以将任何 lib—target 解偶。

你可以通过命令行或者在 tsconfig.json 中提供此选项(推荐):

命令行

  1. tsc --target es5 --lib dom,es6

config.json

  1. "compilerOptions": {
  2. "lib": ["dom", "es6"]
  3. }

lib 分类如下:

  • JavaScript 功能
    • es5
    • es6
    • es2015
    • es7
    • es2016
    • es2017
    • esnext
  • 运行环境
    • dom
    • dom.iterable
    • webworker
    • scripthost
  • ESNext 功能选项
    • es2015.core
    • es2015.collection
    • es2015.generator
    • es2015.iterable
    • es2015.promise
    • es2015.proxy
    • es2015.reflect
    • es2015.symbol
    • es2015.symbol.wellknown
    • es2016.array.include
    • es2017.object
    • es2017.sharedmemory
    • esnext.asynciterable

NOTE

—lib 选项提供非常精细的控制,因此你最有可能从运行环境与 JavaScript 功能类别中分别选择一项,如果你没有指定 —lib,则会导入默认库:

  • —target 选项为 es5 时,会导入 es5, dom, scripthost。
  • —target 选项为 es6 时,会导入 es6, dom, dom.iterable, scripthost。

我个人的推荐:

  1. "compilerOptions": {
  2. "target": "es5",
  3. "lib": ["es6", "dom"]
  4. }

包括使用 Symbol 的 ES5 使用例子:

  1. "compilerOptions": {
  2. "target": "es5",
  3. "lib": ["es5", "dom", "scripthost", "es2015.symbol"]
  4. }

在旧的 JavaScript 引擎时使用 Polyfill

关于此主题的一个视频

要使用一些新功能如 MapSetPromise(随着时间推移会变化),你可以使用现代的 lib 选项,并且需要安装 core-js

  1. npm install core-js --save-dev

接着,在你的项目里导入它:

  1. import 'core-js';

原文: https://jkchao.github.io/typescript-book-chinese/typings/lib.html