现在位置: 首页 > TypeScript 教程 > 正文

TypeScript 声明文件

声明文件是 TypeScript 与 JavaScript 库之间的翻译官,它告诉 TypeScript 一个 JavaScript 库暴露了哪些功能、参数是什么类型、返回值是什么类型。


先从一个问题说起

假设你正在用 TypeScript 写项目,需要引入一个第三方的 JavaScript 库(比如 jQuery),你可能会写出这样的代码:

$('#foo');
// 或
jQuery('#foo');

这段代码在纯 JavaScript 项目中完全没问题,但在 TypeScript 文件中会直接报错:

jQuery('#foo');

// index.ts(1,1): error TS2304: Cannot find name 'jQuery'.

为什么会报错?因为 TypeScript 不认识 $jQuery 是什么。

TypeScript 的核心功能就是类型检查——它在编译阶段就要知道每个变量、每个函数的类型。但 jQuery 是一个纯 JavaScript 库,没有任何类型信息,TypeScript 自然看不懂它。


快速修复:declare 关键字

最简单的方式是用 declare 关键字手动告诉 TypeScript:「这个变量存在,它的类型是这样的」:

declare var jQuery: (selector: string) => any;

jQuery('#foo');

这行代码的含义是:声明一个变量 jQuery,它是一个函数,接收一个 string 类型的参数,返回值可以是任意类型(any)。

现在 TypeScript 就不会报错了,因为编译器已经知道 jQuery 是什么类型。

declare 关键字声明的类型只在编译阶段起作用,编译后的 JavaScript 代码中会被完全删除,不会影响运行时的行为。

上例编译后的 JavaScript 代码为:

jQuery('#foo');

可以看到,declare 语句消失了,只剩下了实际的调用代码。

declare 只能解决单个文件的临时问题。如果一个库有很多方法、很多类,在每个文件里都手写 declare 显然不现实。这就是声明文件的用武之地。


声明文件:一劳永逸的方案

声明文件就是把所有 declare 声明集中放到一个单独的文件里,项目中的任何 TypeScript 文件都可以引用它。

文件命名规范

声明文件统一以 .d.ts 为后缀,d 代表 declaration(声明)。例如:

runoob.d.ts

基本语法

声明一个模块的语法如下:

declare module Module_Name {
}

在 TypeScript 文件中通过三斜线指令引入声明文件:

/// <reference path = "runoob.d.ts" />

三斜线指令是 TypeScript 特有的语法,用于告诉编译器在编译时需要包含指定的声明文件。

很多流行第三方库(如 jQuery、Lodash)的声明文件已经由社区维护好了,存放在 DefinitelyTyped 项目中。你只需要通过 npm 安装对应的 @types/xxx 包即可使用,无需手动编写。


完整实例:从零创建声明文件

下面通过一个完整的例子,演示声明文件的创建和使用全流程。

整个流程涉及以下文件:

文件作用
CalcThirdPartyJsLib.js第三方 JavaScript 库(纯 JS,无类型信息)
Calc.d.ts声明文件(手动编写,描述库的类型)
CalcTest.tsTypeScript 业务代码(引用声明文件,调用库)
CalcTest.js编译产物(tsc 编译后的 JS 文件)
runoob.html浏览器中运行的最终页面

第一步:创建第三方 JavaScript 库

假设我们有一个第三方库,提供了累加求和的功能。它使用命名空间 Runoob 来组织代码,避免变量名冲突:

CalcThirdPartyJsLib.js 文件代码:

// 文件路径:CalcThirdPartyJsLib.js
// 这是一个模拟的第三方 JavaScript 库

// 声明 Runoob 命名空间变量(如果不存在则创建空对象)
var Runoob;

// 使用立即执行函数(IIFE)封装代码,避免内部变量泄漏到全局作用域
(function(Runoob) {
    // Calc 构造函数,用于创建计算器对象
    var Calc = (function () {
        function Calc() {
            // 当前无需初始化参数,保留构造函数以便后续扩展
        }
    })

    // doSum 方法:计算从 0 到 limit 的所有整数之和
    // limit(必填):累加的上限值,包含该值本身
    // 示例:limit=10 时,计算 0+1+2+...+10 = 55
    Calc.prototype.doSum = function (limit) {
        var sum = 0;

        for (var i = 0; i <= limit; i++) {
            sum = sum + i;
        }
        return sum;
    }

    // 将 Calc 构造函数挂载到 Runoob 命名空间下
    // 这样外部就可以通过 new Runoob.Calc() 来创建实例
    Runoob.Calc = Calc;
    return Calc;
})(Runoob || (Runoob = {}));

// 库内部自测代码
var test = new Runoob.Calc();

这个库用到了一个常见模式:立即执行函数(IIFE)。通俗理解,就是把代码包在一个函数里立即运行,这样函数里定义的变量不会跑到外面去,避免和其他代码的变量名冲突。

第二步:编写声明文件

现在我们有了 JS 库,但它没有任何类型信息。需要创建一个声明文件,只描述库的「形状」——有哪些类、有哪些方法、参数和返回值各是什么类型——但不包含任何实际代码逻辑:

Calc.d.ts 文件代码:

// 文件路径:Calc.d.ts
// 这是声明文件,只包含类型信息,不包含任何可执行代码

// 声明 Runoob 模块,与 JS 库中的 Runoob 命名空间对应
declare module Runoob {
   // 声明 Calc 类,告诉 TypeScript 这个类可以被 new 实例化
   export class Calc {
      // 声明 doSum 方法:接收一个 number 参数,返回一个 number
      // 注意:这里只声明了方法的签名,没有方法体(大括号)
      doSum(limit: number): number;
   }
}

声明文件和普通 .ts 文件的最大区别:声明文件只有类型签名,没有实现代码。它只回答「这个函数长什么样」,不回答「这个函数做了什么」。

第三步:在 TypeScript 代码中使用

现在编写业务代码,引用声明文件后,就可以像使用原生 TypeScript 库一样使用这个第三方 JS 库了:

CalcTest.ts 文件代码:

// 文件路径:CalcTest.ts
// 三斜线指令:告诉 TypeScript 编译器引入声明文件
/// <reference path = "Calc.d.ts" />

// 创建 Calc 实例,TypeScript 现在能正确识别 obj 的类型
var obj = new Runoob.Calc();

// obj.doSum("Hello"); // 编译错误!"Hello" 是字符串,而 doSum 要求传入 number
console.log(obj.doSum(10)); // 正确调用:传入 10,期望得到 55

注意被注释掉的那一行:

obj.doSum("Hello");

如果取消这行的注释,TypeScript 编译时会直接报错,因为声明文件中已经规定了 doSum 只接受 number 类型参数。这就是类型检查在保护你——错误在写代码时就暴露出来,而不是等到浏览器运行时才崩溃。

第四步:编译 TypeScript

使用 TypeScript 自带的 tsc 命令编译:

tsc CalcTest.ts

编译后会生成 CalcTest.js 文件,内容如下:

CalcTest.js 编译产物:

// 文件路径:CalcTest.js(tsc 命令自动生成)
/// <reference path = "Calc.d.ts" />
var obj = new Runoob.Calc();
//obj.doSum("Hello"); // 编译错误
console.log(obj.doSum(10)); // 运行结果:在控制台输出 55

可以看到,三斜线指令和声明文件中的类型信息在编译产物中都不见了,只剩下纯粹的 JavaScript 运行代码。

第五步:在浏览器中运行

最后,创建一个 HTML 页面把所有 JS 文件串联起来:

实例

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<!-- 第 1 步:加载第三方库,让 Runoob 命名空间在全局可用 -->
<script src = "CalcThirdPartyJsLib.js"></script>
<!-- 第 2 步:加载编译后的业务代码 -->
<script src = "CalcTest.js"></script>
</head>
<body>
    <h1>声明文件测试</h1>
    <p>菜鸟测试一下。</p>
</body>
</html>

用浏览器打开这个 HTML 文件,打开控制台(F12),可以看到输出结果:

55

这就是 doSum(10) 的返回值:0+1+2+...+10 = 55。

声明文件测试结果


小结

声明文件本质上是一份「类型说明书」,让 TypeScript 能够理解和检查纯 JavaScript 库。

整个工作流程可以概括为三步:

1. 拿到一个 JS 库,分析它暴露了哪些 API。

2. 编写 .d.ts 声明文件,描述这些 API 的类型签名。

3. 在 TypeScript 代码中引用声明文件,即可享受完整的类型检查保护。

对于日常开发中常用的第三方库,绝大多数已经有现成的声明文件(通过 npm install @types/xxx 安装)。只有当你用到非常冷门的库或内部私有库时,才需要手动编写声明文件。