# 声明文件

* 从类型type角度分为：基本类型（string、number、boolean等）及其混合；复杂类型（class、function、object）及其混合（比如说又是class又是function）。
* 从代码有效范围分为：全局变量、模块变量和又是全局变量又是模块变量的。
* 从定义文件来说：自己写的`.d.ts`文件和扩展别人写的`.d.ts`文件。

## 支持语法

```
declare var             // 声明全局变量
declare function        // 声明全局方法
declare class           // 声明全局类
declare enum            // 声明全局枚举类型
declare namespace       // 声明（含有子属性的）全局对象
interface || type       // 声明全局类型
export                  // 导出变量
export namespace        // 导出（含有子属性的）对象
export default          // ES6 默认导出
export =                // commonjs 导出模块
export as namespace     // UMD 库声明全局变量
declare global          // 扩展全局变量
declare module          // 扩展模块
/// <reference />       // 三斜线指令
```

## 声明文件应用场景

主要是为了解决在ts中第三方组件库或者插件中，第三方库引用的问题。

例如，代码中用到了`jQuery`，需要到 `.d.ts` 文件中声明，不然TS语法会报找不到全局变量 `jquery`和`$`对象；

// shime-global.d.ts

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

然后在改目录下所有ts中使用 `jQuery`, `$` 对象是，ts便不会报错。

```typescript
jQuery('#foo');
```

上例中，declare var 并没有真的定义一个变量，只是定义了全局变量 jQuery 的类型，仅仅会用于编译时的检查，在编译结果中会被删除。它编译结果是：

```typescript
jQuery('#foo');
```

补充：上面声明文件为什么是`.d.ts`,这是ts模块解析规定的，先从`.ts`文件中查找，然后再查找 `.d.ts`文件中查找。

先让我们看看在工程中一般有哪些声明：

我们经常这样配置全局变量,如果第三方库没有声明，当前代码中没有定义的，都需要声明。

vue 模块的声明

```typescript
// shims-vue.d.ts

declare module '*.vue' {
  import Vue from 'vue';
  export default Vue;
}
```

全局变量 模块

```typescript
// shmie-global.d.ts

declare var window: Window;
declare var document: Document;

declare module "element-ui";

// ...
```

那么有些同学就要问了，我们用的很多npm，`npm install`之后，好像也没有声明，在ts中使用编译也没有报错啊。那是因为npm中已经声明了。

## 书写声明文件

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

第三方库有以下几种使用场景

* 全局变量：通过  标签引入第三方库，注入全局变量
* npm 包：通过 import foo from 'foo' 导入，符合 ES6 模块规范
* UMD 库：既可以通过  标签引入，又可以通过 import 导入
* 直接扩展全局变量：通过  标签引入后，改变一个全局变量的结构
* 在 npm 包或 UMD 库中扩展全局变量：引用 npm 包或 UMD 库后，改变一个全局变量的结构
* 模块插件：通过  或 import 导入后，改变另一个模块的结构

### 全局变量的声明

全局变量的声明主要用以下几种

* `declare var` 声明全局变量
* `declare function` 声明全局方法
* `declare class` 声明全局类
* `declare enum` 声明全局枚举类型
* `declare namespace` 声明（含有子属性的）全局对象
* `interface` 和 `type` 声明全局类型

举个例子：声明一个class 对象 Animal

```typescript
// Animal.d.ts 

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

```typescript
// src/index.ts

let cat = new Animal('Tom');
```

### npm包的声明

一般来说，npm 包的声明文件可能存在于两个地方：

1. 与该 npm 包绑定在一起。判断依据是 `package.json` 中有 types 字段，或者有一个 `index.d.ts` 声明文件。这种模式不需要额外安装其他包，是最为推荐的，所以以后我们自己创建 npm 包的时候，最好也将声明文件与 npm 包绑定在一起。

![](https://img.xiaao.xin/image/M00/00/01/L2Kexl5FTPyAT-CxAAG3koAL6kQ931.png)

1. 发布到 @types 里。我们只需要尝试安装一下对应的 @types 包就知道是否存在该声明文件，安装命令是 `npm install @types/foo --save-dev`。这种模式一般是由于 npm 包的维护者没有提供声明文件，所以只能由其他人将声明文件发布到 @types 里了。

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

有两种方案，一种自己写`@types/xxx`,发布上去。另一个直接在工程目录下声明，建议新建一个目录`types`，声明文件放在`types`中

#### npm中声明和全局变量声明的区别

npm 包的声明文件与全局变量的声明文件有很大区别。在 npm 包的声明文件中，使用 `declare` 不再会声明一个全局变量，而只会在当前文件中声明一个局部变量。只有在声明文件中使用 `export` 导出，然后在使用方 `import` 导入后，才会应用到这些类型声明。

```typescript
// 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;
}
```

```typescript
// src/index.ts

import { name, getName, Animal, Directions, Options } from 'foo';
```

当然也可以混用

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

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

上面 `Axios`就是这样做的。

### UMD库中的声明

### 在 npm 包或 UMD 库中扩展全局变量

对于 npm 包或 UMD 库，如果导入此库之后会扩展全局变量，则需要使用另一种语法在声明文件中扩展全局变量的类型，那就是 `declare global`

```typescript
// types/foo/index.d.ts

declare global {
    interface String {
        prependHello(): string;
    }
}

export {};


// src/index.ts
'bar'.prependHello();
```

### 声明模块插件

## 发布声明文件

两种方式

* 将声明文件和源码放在一起
* 发布到`@types`中

### 将声明文件和源码放在一起

* 自动生成，tsc自动生成
* 手动声明

如果是手动写的声明文件，那么需要满足以下条件之一，才能被正确的识别：

* 给 `package.json` 中的 `types` 或 `typings` 字段指定一个类型声明文件地址
* 在项目根目录下，编写一个 `index.d.ts` 文件
* 针对入口文件（`package.json` 中的 `main` 字段指定的入口文件），编写一个同名不同后缀的 `.d.ts` 文件

```javascript
{
    "name": "foo",
    "version": "1.0.0",
    "main": "lib/index.js",
    "types": "foo.d.ts",
}
```

如果没有指定`types`或`typings`，那么就会在根目录下寻找 `index.d.ts` 文件，将它视为此库的类型声明文件。

如果没有找到 `index.d.ts` 文件，那么就会寻找入口文件（`package.json` 中的 `main` 字段指定的入口文件）是否存在对应同名不同后缀的 `.d.ts` 文件。

如果还是不存在，那么就会寻找是否存在 `lib/index.d.ts` 文件。假如说连 `lib/index.d.ts` 都不存在的话，就会被认为是一个没有提供类型声明文件的库了。

## 参考

[typescript-tutorial](https://ts.xcatliu.com/basics/declaration-files)
