TS 的声明文件

2,857 阅读6分钟

打算为你的团队写一个封装好的工具?TS的声明文件是必不可少的,它不仅仅让你的工具支持TS,更是负责充当一个说明书的作用,让人对其的使用一目了然。


  • 什么是声明语句?什么时候需要他?

假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过script 标签引入 jQuery,然后就可以使用全局变量 $ 或 jQuery 了。 我们通常这样获取一个 id 是 foo 的元素:

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

// ts并不了解这两个变量从何来,到哪去。我们可以告诉ts的编译器,这个东西大概是什么
// 其用于编译时的检查,在编译结果中会被删除
declare var jQuery: (selector: string) => any;
jQuery('#foo');

这就是声明语句。如果你是js编写的,往往需要一个声明文件。

如果你是ts编写的,那么让ts自动生成就好了。

以下教程根据js的库进行编写声明文件。

  • 什么是声明文件?

你可以把声明语句看作是声明文件的“组件”。声明语句组成声明文件。

通常我们会把声明语句放到一个单独的文件{(jQuery.d.ts)声明文件必需以 .d.ts 为后缀。}中,这就是声明文件

// src/jQuery.d.ts

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

一般来说,ts 会解析项目中所有的 *.ts 文件, .d.ts 的文件也属于*.ts文件。

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

假如仍然无法解析,那么可以检查下 tsconfig.json 。

这里只演示了全局变量模式的声明文件,通过模块导入的方式的话,那么引入声明文件又是另一种方式了。

  • 书写声明文件!

一般来说第三方库都会提供声明文件,我们只需要安装它就好了。

但有一些库不会提供声明文件,我们就需要自己书写声明文件了。

前面只介绍了最简单的声明文件内容,而真正书写一个声明文件并不是一件简单的事

书写声明文件有多种场景需要契合,例如npm导入和script导入的声明文件的写法与使用方法都不一样,我们这也知会讨论比较常用的这两种方式,如果看官有兴趣,可以自行移步教程学习。

1. 全局变量:通过 script 标签引入第三方库,注入全局变量

全局变量是最简单的一种场景,之前举的例子就是通过 script 标签引入 jQuery,注入全局变量 $ 和 jQuery。

使用全局变量的声明文件时,如果是以 npm install @types/xxx --save-dev 安装的,则不需要任何配置。

如果是将声明文件直接存放于当前项目中,则建议和其他源码一起放到 src 目录下(或者对应的源码目录下):

/path/to/project
├── src
|  ├── index.ts
|  └── jQuery.d.ts
└── tsconfig.json

如果没有生效,可以检查下 tsconfig.json 中的 files、include 和 exclude 配置,确保其包含了 jQuery.d.ts 文件。

全局变量的声明文件主要有以下几种语法:

1. declare var / let / const声明全局变量

没什么区别 const定义的无法修改。
```
//使用const的时候是最多的,一般不允许他人修改你的函数
declare const jQuery: (selector: string) => any;
```

2. declare function 声明全局方法

```
declare function jQuery(selector: string): any;
```

3. declare class 声明全局类

```
declare class Animal {
    name: string;
    constructor(name: string);
    sayHi(): string;
}

//其他文件中
let cat = new Animal('Tom');
```

4. declare enum 声明全局枚举类型

不讨论,这个需要后面的知识

5. declare namespace 声明(含有子属性的)全局对象

declare namespace 还是比较常用的,它用来表示全局变量是一个对象,包含很多子属性。 举个例子:

//jQuery 是一个全局变量,它是一个对象
//jQuery.ajax 方法可以调用
//那么我们就应该使用 declare namespace jQuery 来声明这个属性的全局变量。
declare namespace jQuery {
    function ajax(url: string, settings?: any): void;
}

//更为复杂一点的例子
declare namespace jQuery {
    function ajax(url: string, settings?: any): void;
    const version: number;
    class Event {
        blur(eventType: EventType): void
    }
    enum EventType {
        CustomClick
    }
}

当然嵌套也是经常看到的事情,我们也可以使用namespace嵌套使用

declare namespace jQuery {
    function ajax(url: string, settings?: any): void;
    namespace fn {
        function extend(object: any): void;
    }
}

6. interface 和 type 声明全局类型

除了主要的全局变量之外,可能有一些类型我们也希望能暴露出来。

在类型声明文件中,我们可以直接使用 interface 或 type 来声明一个全局的接口或类型:

// src/jQuery.d.ts
interface AjaxSettings {
    method?: 'GET' | 'POST'
    data?: any;
}
declare namespace jQuery {
    function ajax(url: string, settings?: AjaxSettings): void;
}

2. npm 包:通过 import foo from 'foo' 导入,符合 ES6 模块规范

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

  1. 与该 npm 包在一起。package.json中有types字段,或有一个index.d.ts声明文件。这种模式不需要额外安装其他包,是最为推荐的,所以以后我们自己创建 npm 包的时候,最好也将声明文件与 npm 包绑定在一起。
  2. 发布到 @types 里。我们只需要尝试安装一下对应的 @types 包就知道是否存在该声明文件,安装命令是 npm install @types/foo --save-dev。这种模式一般是由于 npm 包的维护者没有提供声明文件,所以只能由其他人将声明文件发布到 @types 里了。

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


由于是通过 import 语句导入的模块,所以声明文件存放的位置也有所约束,一般有两种方案:

  1. 创建一个 node_modules/@types/foo/index.d.ts 文件,存放 foo 模块的声明文件。这种方式不需要额外的配置,但是 node_modules 目录不稳定,代码也没有被保存到仓库中,无法回溯版本,有不小心被删除的风险,故不太建议用这种方案,一般只用作临时测试。
  2. 创建一个 types 目录,专门用来管理自己写的声明文件,将 foo 的声明文件放到 types/foo/index.d.ts 中。这种方式需要配置下 tsconfig.json 中的 paths 和 baseUrl 字段。
介绍一下第二种方案
├── src
|  └── index.ts
├── types
|  └── foo
|     └── index.d.ts
└── tsconfig.json

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

npm 包的声明文件主要有以下几种语法:

1. export 导出变量

在 npm 包的声明文件中,使用 declare 不再会声明一个全局变量,而只会在当前文件中声明一个局部变量。

只有在声明文件中使用 export 导出,然后在使用方 import 导入后,才会应用到这些类型声明。

  1. export namespace 导出(含有子属性的)对象
// 某声明文件
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;
}


// 某使用文件
import { name, getName, Animal, Directions, Options } from 'foo';

console.log(name);
let myName = getName();
let cat = new Animal('Tom');
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
let options: Options = {
    data: {
        name: 'foo'
    }
};
  1. export default ES6 默认导出
  2. export = commonjs 导出模块

。。。烂尾,感觉没啥好写的,很好理解

内容从此处摘抄
https://ts.xcatliu.com/basics/declaration-files.html#%E4%B9%A6%E5%86%99%E5%A3%B0%E6%98%8E%E6%96%87%E4%BB%B6