使用声明文件

TypeScript 中的 .d.ts 声明文件主要有以下几种来源:

  • TypeScript 语言内置的声明文件。

  • 安装的第三方声明文件。

  • 自定义的声明文件。

语言内置的声明文件

当我们在计算机中安装了 TypeScript 语言后,同时也安装了一些语言内置的声明文件,它们位于 TypeScript 语言安装目录下的 lib 文件夹中。下面列举了部分内置的声明文件:

lib.d.ts
lib.dom.d.ts
lib.es2015.d.ts
lib.es2016.d.ts
lib.es2017.d.ts
lib.es2018.d.ts
lib.es2019.d.ts
lib.es2020.d.ts
lib.es5.d.ts
lib.es6.d.ts

TypeScript 语言内置的声明文件统一使用 lib.[description].d.ts 命名方式,其中,description 部分描述了该声明文件的内容。在这些声明文件中,既定义了标准的 JavaScript API,如 Array API、Math API 以及 Date API 等,也定义了特定于某种 JavaScript 运行环境的 API,如 DOM API 和 Web Workers API 等。

TypeScript 编译器在编译代码时能够自动加载内置的声明文件。因此,我们可以在代码中直接使用那些标准 API,而不需要进行特殊的配置。例如,我们可以在代码里直接使用标准的 DOM 方法,TypeScript 能够从内置的声明文件 lib.dom.d.ts 中获取该方法的类型信息并进行类型检查。示例如下:

const button = document.getElementById('btn');

第三方声明文件

如果我们的工程中使用了某个第三方代码库,例如 jQuery,通常我们也想要安装该代码库的声明文件。这样,TypeScript 编译器就能够对代码进行类型检查,同时代码编辑器也能够根据声明文件中的类型信息来提供代码自动补全等功能。

在尝试安装某个第三方代码库的声明文件时,可能会遇到以下三种情况,下面以 jQuery 为例:

  • 在安装 jQuery 时,jQuery 的代码包中已经内置了它的声明文件。

  • 在安装 jQuery 时,jQuery 的代码包中没有内置的声明文件,但是在 DefinitelyTyped 网站上能够找到 jQuery 的声明文件。

  • 通过以上方式均找不到 jQuery 的声明文件,需要自定义 jQuery 的声明文件。

含有内置声明文件

实际上,在 jQuery 的代码包中没有包含内置的声明文件。本节我们将以 RxJS 代码库为例,在 RxJS 的代码包中内置了 TypeScript 声明文件。RxJS 是一个支持响应式编程的代码库,主要用于处理基于事件的异步数据流。在开始之前需要安装 Node.js 并对工程进行简单的初始化。关于 Node.js 环境配置的详细介绍请参考第 9 章。

假设当前工程目录结构如下:

C:\app
|-- index.ts
|-- package.json
`-- tsconfig.json

C:\app 目录下运行 npm 命令来安装 RxJS,示例如下:

npm install rxjs

在安装后,工程目录结构如下:

C:\app
|-- index.ts
|-- node_modules
|   |-- rxjs
|   |   |-- <省略了部分文件>
|   |   |-- index.d.ts
|   |   |-- index.js
|   |   |-- package.json
|   |   `-- README.md
|   `-- <省略了部分文件>
|-- package.json
`-- tsconfig.json

C:\app\node_modules\rxjs 目录中存在一个 index.d.ts 文件,它就是 RxJS 代码库内置的 TypeScript 声明文件。通常来讲,若代码库的安装目录中包含 .d.ts 文件,则说明该代码库提供了内置的声明文件。

C:\app\index.ts 中使用 RxJS 时,编译器能够正确地进行类型检查并提供智能提示功能。示例如下:

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
    subscriber.next(1);
    setTimeout(() => {
        subscriber.next(2);
        subscriber.complete();
    }, 1000);
});

observable.subscribe({
    next(x) {
        console.log('got value ' + x);
    },
    error(err) {
        console.error('something wrong occurred: ' + err);
    },
    complete() {
        console.log('done');
    }
});

此例第 1 行,从 rxjs 模块中导入了 Observable 对象,编译器能够自动地从 C:\app\node_modules\rxjs\index.d.ts 文件中读取 Observable 的类型声明。

DefinitelyTyped

DefinitelyTypedhttps://definitelytyped.org/ )是一个公开的集中式的 TypeScript 声明文件代码仓库,该仓库中包含了数千个代码库的声明文件。DefinitelyTyped 托管在 GitHub 网站上,由开源社区和 TypeScript 开发团队共同维护。

如果我们正在使用的第三方代码库没有内置的声明文件,那么可以尝试在 DefinitelyTyped 仓库中搜索声明文件。例如,jQuery 没有内置的声明文件,我们可以前往 DefinitelyTyped 的搜索页面去搜索关键字 jQuery,如图7-2所示。

image 2024 05 17 13 57 16 298
Figure 1. 图7-2 DefinitelyTyped jQuery

因为 DefinitelyTyped 仓库中提供了 jQuery 的声明文件,因此将跳转到 npmjQuery 声明文件主页,即 @types/jquery 代码包。值得一提的是,DefinitelyTyped 仓库中的所有声明文件都会发布到 npm@types 空间下,如图7-3所示。

image 2024 05 17 13 58 17 811
Figure 2. 图7-3 @types/jquery

接下来,让我们安装 jQuery 的声明文件,即 @types/jquery 代码包。在开始之前需要安装 Node.js 并对工程进行简单的初始化。关于 Node.js 环境配置的详细介绍请参考第 9 章。

假设当前工程目录结构如下:

C:\app
|-- index.ts
|-- package.json
`-- tsconfig.json

C:\app 目录下使用 npm 命令来安装 @types/jquery 声明文件代码包,如下所示:

npm install @types/jquery

安装后,工程目录结构如下:

C:\app
|-- index.ts
|-- node_modules
|   `-- @types
|       |-- jquery
|       |   |-- <省略了部分文件>
|       |   |-- index.d.ts
|       |   `-- package.json
|       `-- <省略了部分文件>
|-- package.json
|-- package-lock.json
`-- tsconfig.json

jQuery 的声明文件被安装到了 C:\app\node_modules\@types\jquery 目录下,该目录下的 index.d.ts 文件就是 jQuery 的声明文件。

C:\app\index.ts 中使用 jQuery API 时,编译器能够从安装的声明中获取 jQuery 的类型信息并正确地进行类型检查。示例如下:

import * as $ from 'jquery';

$('p').show();

typings与types

每个 npm 包都有一个标准的 package.json 文件,该文件描述了当前 npm 包的基础信息。例如,jQuery 包中 package.json 文件的主要内容如下:

{
    "name": "jquery",
    "description": "JavaScript library for DOM operations",
    "version": "4.0.0-pre",
    "main": "dist/jquery.js",
    "homepage": "https://jquery.com",
    "author": {
       "name": "JS Foundation and other contributors",
    },
    "repository": {
        "type": "git",
        "url": "https://github.com/jquery/jquery.git"
    },
    "keywords": [
        "jquery",
        "javascript",
        "browser",
        "library"
    ],
    "bugs": {
        "url": "https://github.com/jquery/jquery/issues"
    },
    "license": "MIT",
    "dependencies": {},
    "devDependencies": {},
    "scripts": {}
}

该文件中比较重要的属性有表示包名的 name 属性、表示版本号的 version 属性和表示入口脚本的 main 属性等。

TypeScript 扩展了 package.json 文件,增加了 typings 属性和 types 属性。虽然两者的名字不同,但是作用相同,它们都用于指定当前 npm 包提供的声明文件。

假设有如下目录结构的工程:

C:\my-package
|-- index.js
|-- index.d.ts
`-- package.json

package.json 文件的内容如下:

{
    "name": "my-package",
    "version": "1.0.0",
    "main": "index.js",
    "typings": "index.d.ts"
}

此例中,使用 typings 属性定义了 my-package 包的声明文件为 index.d.ts 文件。当 TypeScript 编译器进行模块解析时,将会读取该属性的值并使用指定的 index.d.ts 文件作为声明文件。这里我们也可以将 typings 属性替换为 types 属性,两者是等效的。关于模块解析的详细介绍请参考 7.9 节。

如果一个 npm 包的声明文件为 index.d.ts 且位于 npm 包的根目录下,那么在 package.json 文件中也可以省略 typings 属性和 types 属性,因为编译器在进行模块解析时,若在 package.json 文件中没有找到 typings 属性和 types 属性,则将默认使用名为 index.d.ts 的文件作为声明文件。因此,上例中的 package.json 文件可以修改为如下形式:

{
    "name": "my-package",
    "version": "1.0.0",
    "main": "index.js"
}

typesVersions

每个声明文件都有其兼容的 TypeScript 语言版本。例如,如果一个声明文件中使用了 TypeScript 3.0 才开始支持的 unknown 类型,那么在使用该声明文件时,需要安装 TypeScript 3.0 或以上的版本。也就是说,将该声明文件提供给其他用户使用时,需要使用者安装 TypeScript 3.0 或以上的版本,这可能会给使用者带来困扰。

在 TypeScript 3.1 版本中,编译器能够根据当前安装的 TypeScript 版本来决定使用的声明文件,该功能是通过 package.json 文件中的 typesVersions 属性来实现的。

假设有如下目录结构的工程:

C:\my-package
|-- ts3.1
|   `-- index.d.ts
|-- ts3.7
|   `-- index.d.ts
|-- index.d.ts
`-- index.js

package.json 文件的内容如下:

{
    "name": "my-package",
    "version": "1.0.0",
    "main": "index.js",
    "typings": "index.d.ts",
    "typesVersions": {
        ">=3.7": {
            "*": ["ts3.7/*"]
        },
        ">=3.1": {
            "*": ["ts3.1/*"]
        }
    }
}

此例中,我们定义了两个声明文件匹配规则:

  • 第 7 行,当安装了 TypeScript 3.7 及以上版本时,将使用 ts3.7 目录下的声明文件。

  • 第 10 行,当安装了 TypeScript 3.1 及以上版本时,将使用 ts3.1 目录下的声明文件。

需要注意的是,typesVersions 中的声明顺序很关键,编译器将从第一个声明(此例中为 ">=3.7")开始尝试匹配,若匹配成功,则应用匹配到的值并退出。因此,若将此例中的两个声明调换位置,则会产生不同的结果。

此外,如果 typesVersions 中不存在匹配的版本,如当前安装的是 TypeScript 2.0 版本,那么编译器将使用 typings 属性和 types 属性中定义的声明文件。

自定义声明文件

如果使用的第三方代码库没有提供内置的声明文件,而且在 DefinitelyTyped 仓库中也没有对应的声明文件,那么就需要开发者自己编写一个声明文件。

在 7.7 节中介绍了如何编写声明文件,但如果我们不想编写一个详尽的声明文件,而只是想要跳过对某个第三方代码库的类型检查,则可以使用下面介绍的方法。

我们还是以 jQuery 为例,只不过不再安装 DefinitelyTyped 提供的 jQuery 声明文件,而是自定义一个 jQuery 声明文件,让编译器不对 jQuery 进行类型检查。

假设当前工程目录结构如下:

C:\app
|-- index.ts
|-- package.json
`-- tsconfig.json

接下来,我们在 C:\app 目录下创建一个 .d.ts 声明文件,例如 jquery.d.tsjquery.d.ts 声明文件的内容如下:

declare module 'jquery';

此例中的代码是外部模块声明,该声明会将 jquery 模块的类型设置为 any 类型。

C:\app\index.ts 文件中,可以通过如下方式使用 jQuery 声明文件:

import * as $ from 'jquery';

$('p').show();
//     ~~~~
//     类型为:any

此例中,jquery 模块中所有成员的类型都成了 any 类型,这等同于不对 jQuery 进行类型检查。