# 声明文件

当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。

当一个第三方库只是一个 JavaScript 库的时候,为了提升在 TypeScript 的开发体验,我们就需要对这个第三方库进行类型声明。

# 声明语法索引

全局

declare var 声明全局变量 declare function 声明全局方法 declare class 声明全局类

拓展

declare global 扩展全局变量 declare module 扩展模块

# 什么是声明语句

假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script> 标签引入 jQuery,然后就可以使用全局变量 $ 或 jQuery 了。

我们通常这样获取一个 id 是 foo 的元素:

$("#foo");
// or
jQuery("#foo");

但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西 1:

jQuery("#foo");
// ERROR: Cannot find name 'jQuery'.

这时,我们需要使用 declare var 来定义它的类型 2:

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

jQuery("#foo");

# 什么是声明文件

通常我们会把声明语句放到一个单独的文件(jQuery.d.ts)中,这就是声明文件 3:

// src/jQuery.d.ts

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

声明文件必需以 .d.ts 为后缀。

一般来说,ts 会解析项目中所有的 *.ts 文件,当然也包含以 .d.ts 结尾的文件。

所以当我们将 jQuery.d.ts 放到项目中时,其他所有 *.ts 文件就都可以获得 jQuery 的类型定义了。

当然,jQuery 的声明文件不需要我们定义了,社区已经帮我们定义好了,我们可以直接下载下来使用。

npm install @types/jquery --save-dev

# 编写声明文件

当一个第三方库没有提供声明文件时,我们就需要自己书写声明文件了

TIP

这里暂时只关注对 npm 包的声明

一般我们通过 import foo from 'foo' 导入一个 npm 包,这是符合 ES6 模块规范的。

在我们尝试给一个 npm 包创建声明文件之前,需要先看看它的声明文件是否已经存在。一般来说,npm 包的声明文件可能存在于两个地方

  • 与该 npm 包绑定在一起。判断依据是 package.json 中有 types 字段,或者有一个 index.d.ts 声明文件
  • 发布到 @types 里。我们只需要尝试安装一下对应的 @types 包就知道是否存在该声明文件

假如以上两种方式都没有找到对应的声明文件,那么我们就需要自己为它写声明文件了

一般建议创建一个 types 目录,专门用来管理自己写的声明文件

比如将 foo 的声明文件放到 types/foo/index.d.ts 中。这种方式需要配置下 tsconfig.json 中的 paths 和 baseUrl 字段。

{
  "compilerOptions": {
    "module": "commonjs",
    "baseUrl": "./",
    "paths": {
      "*": ["types/*"]
    }
  }
}

export

export 的语法与普通的 ts 中的语法类似,区别仅在于声明文件中禁止定义具体的实现

// types/foo/index.d.ts

export const name: string;
export function getName(): string;
export class Animal {
  constructor(name: string);
  sayHi(): string;
}
export enum Directions {
  Up,
  Down,
  Left,
  Right
}
export interface Options {
  data: any;
}

混用 declare 和 export

我们也可以使用 declare 先声明多个变量,最后再用 export 一次性导出

// types/foo/index.d.ts

declare const name: string;
declare function getName(): string;
declare class Animal {
  constructor(name: string);
  sayHi(): string;
}
declare enum Directions {
  Up,
  Down,
  Left,
  Right
}
// interface 前是不需要 declare 的
interface Options {
  data: any;
}

export { name, getName, Animal, Directions, Options };

export namespace

export namespace 用来导出一个拥有子属性的对象

// types/foo/index.d.ts
export namespace foo {
  const name: string;
  namespace bar {
    function baz(): string;
  }
}
// src/index.ts
import { foo } from "foo";

console.log(foo.name);
foo.bar.baz();

export default

在 ES6 模块系统中,使用 export default 可以导出一个默认值

// types/foo/index.d.ts
export default function foo(): string;
// src/index.ts
import foo from "foo";

foo();

注意,只有 function、class 和 interface 可以直接默认导出,其他的变量需要先定义出来,再默认导出

# 发布声明文件