使用声明文件
TypeScript 声明文件的作用是描述 JavaScript 模块内各个声明的类型信息,IDE 或编译器在获得这些信息后,就提供代码补全、接口提示、类型检查等功能。
声明文件以 .d.ts
为扩展名,它不包含任何具体实现代码(如函数体或变量具体值),仅包含类型声明,接下来进行详细介绍。
使用声明文件的原因
通常来说,项目中的历史代码或第三方库都是用 JavaScript 编写的,由于 JavaScript 本身并不包含类型信息,因此直接将历史代码或第三方库引入 TypeScript 项目中可能引起编译错误。
假设当前项目结构如下。
D:\TSProject
library.js
index.ts
tsconfig.json
library.js
是一段历史代码,它用 JavaScript 编写,包含一个公共函数 sum()
,用于计算两数之和,其代码如下。
export function sum(a, b) {
return a + b;
}
index.ts
文件如下,它引用了 library.js
文件,然后输出 1 和 2 相加的结果。
import { sum } from './library.js'
console.log(sum(1, 2));
tsconfig.json
文件如下,它使用了严格模式。关于严格模式的更多信息,请参考18.1.5节。
{
"compilerOptions": {
"strict": true,
"outDir": "dist"
}
}
由于使用了严格模式,TypeScript 编译器要求每个变量、方法都必须有明确的类型,不允许各个声明隐式具有 any
类型,因此 index.ts
文件的第一行代码将引起编译错误,如图21-1所示。

要快速解决这个问题,有几种办法,例如,编辑 tsconfig.json
文件,去掉 "strict":true,但这样所有文件(包括 TypeScript 文件)都无法使用严格模式的检查,这显然不是我们需要的。
另一种办法是单独为 JavaScript 忽略各项编译检查,例如,在 tsconfig.json
文件中增加编译选项 "allowJs": true,代码如下。
{
"compilerOptions": {
"strict": true,
"outDir": "dist",
"allowJs": true
}
}
该编译选项将告知编译器将 JavaScript 文件中的所有声明都当作 any 类型而不做其他要求。
但这只最低限度地保证 JavaScript 文件能够正常运行。因为其声明全是 any
类型,无法使用代码补全、接口提示、类型检查等功能,所以用这些声明进行开发不仅效率低,而且容易出错。这些错误通常只能在程序运行后才能发现,排错成本较大。如果要描述 JavaScript 中的各个声明真实的类型,就需要定义声明文件。例如,编写 library.d.ts
文件,描述 sum()
函数的各个参数的类型及返回值类型。library.d.ts
的代码如下。
declare function sum(a: number, b: number): number
export { sum }
在这之后,index.ts
文件将不再出现编译错误,并且也可以看到在调用 sum()
函数时,IDE 能够识别出 sum()
函数的参数类型及返回值类型。图21-2所示为 IDE 智能提示。

为JavaScript编写声明文件
声明文件以 .d.ts
为扩展名,它不包含任何具体实现代码(如函数体或变量具体值),仅包含类型声明。它通过 declare
关键字声明 JavaScript 中需要指明类型的对象。
声明形式通常有以下几种。
-
declare var/let/const:声明全局变量。
-
declare function:声明全局函数。
-
declare class:声明全局类。
-
declare enum:声明全局枚举类型。
-
declare namespace:声明(含有子属性的)对象。
-
declare module:声明模块。
声明模块将在21.2.3节中单独介绍,其他声明形式将在本节中详细介绍。
声明全局变量和全局方法
假设 lib.js
文件的内容如下。其中,声明并导出了一个名为 pi 的常量,然后声明并导出了一个计算圆形面积的函数 calculatingArea()
,它需要一个参数 r
(圆形半径),返回圆形面积(计算公式为半径的平方乘以圆周率)。
export const pi = 3.14159;
export function calculatingArea(r) {
return r * r * pi;
}
阅读上述 JavaScript 代码,你可以发现 pi
及 r
都是数值类型。接下来,根据这些信息,编写对应的声明文件 lib.d.ts
,文件内容如下。
declare const pi: number;
declare function calculatingArea(r: number): number;
export { pi, calculatingArea }
声明全局枚举及函数
假设 lib.js
文件的内容如下。其中,声明了一个名为 printDirection()
的函数,用于输出方向信息,它要求传入一个参数 dirt
(方向),然后根据传入的值,输出不同的方向文本。
export function printDirection(dirt) {
switch (dirt) {
case 0:
console.log("up");
break;
case 1:
console.log("down");
break;
case 2:
console.log("left");
break;
case 3:
console.log("right");
break;
}
}
阅读上述 JavaScript 代码,你可以发现 printDirection()
函数的参数 dirt
实际上是一个枚举类型。接下来,根据这些信息,编写对应的声明文件 lib.d.ts
,声明一个表示方向的常量枚举 direction
及 printDirection()
函数,函数的参数为 direction
类型。文件内容如下。
export declare const enum direction {
up = 0,
down = 1,
left = 2,
right = 3
}
export declare function printDirection(dirt: direction): void;
使用接口或类型别名
在类型声明文件中直接使用 interface
或 type
来声明一个全局的接口或类型。假设 lib.js
文件的内容如下。其中,声明了一个 printPoint()
函数用于输出坐标,它要求传入一个参数 point
(表示坐标),然后在函数体中输出 x
值和 y
值。
export function printPoint(point) {
console.log(`location is ${point.x},${point.y}.`);
}
阅读上述 JavaScript 代码,你可以发现 point
参数实际上具有一定的结构要求,因此可以用 interface
或 type
来声明该结构。接下来,根据这些信息,编写对应的声明文件 lib.d.ts
,文件内容如下。
interface Point { x: number; y: number; }
//type Point = { x: number; y: number; } //使用类型别名的方式声明
export declare function printPoint(point: Point): void;
声明全局类
假设 lib.js
文件的内容如下。
export class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get name() {
return `${this.firstName} ${this.lastName}`;
}
selfIntroduction() {
console.log(`My name is ${this.name}`);
}
}
其中声明了一个名为 Person
的类,它不仅具有一个构造函数(分别传入 firstName
和 lastName
),还具有一个存取器 name
(用来返回完整姓名),以及一个名为 selfIntroduction()
的函数(用于输出自我介绍文本)。
阅读上述代码,你可以发现类的各个属性及存取器均为 string
类型,而 selfIntroduction()
函数没有返回值,因此为 void
类型。接下来,编写对应的声明文件 lib.d.ts
,文件内容如下。
export declare class Person {
get name(): string;
firstName: string;
lastName: string;
constructor(firstName: string, lastName: string);
selfIntroduction(): void;
}
声明(含子属性的)对象
此声明方式通常用于闭包。假设 lib.js
文件的内容如下。
export var excelHelper;
(function (excelHelper) {
excelHelper.fileName = "D:\\x.xls";
function readExcelCell(row, col) {
//...
return cellValue;
}
excelHelper.readExcelCell = readExcelCell;
})(excelHelper || (excelHelper = {}));
它声明并导出了一个对象 excelHelper
,用于读取 excel
信息,然后声明了一个闭包,并将闭包中局部变量和函数的值通过属性和方法的形式赋给 excelHelper
对象。
接下来,编写对应的声明文件 lib.d.ts
。文件内容如下。
export declare namespace excelHelper {
let fileName: string;
function readExcelCell(row: number, col: number): string;
}
为TypeScript生成声明文件
TypeScript 文件最终被编译为 JavaScript
文件才能执行。如果这些库文件需要供其他团队使用,相对于同时提供该库的 TypeScript 源文件和 JavaScript 文件,只提供 TypeScript 声明文件及 JavaScript 文件更具优势,因为使用后一种方式不仅文件占用的空间小,而且无须再编译代码。
TypeScript 编译器支持编译时自动生成 .d.ts
文件,只需要在 tsconfig.json
配置文件中开启 declaration
编译选项即可。然后,在使用 tsc
命令编译时,不仅会输出 JavaScript 文件,还会输出目录生成的同名 .d.ts
文件。
{
"compilerOptions": {
"declaration": true
}
}