TypeScript入门之申明文件

482 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情

简介

申明文件在我们平时开发中用到的并不多,但是当我们要开源一个库的时候,就需要我们写申明文件了。

那什么是申明文件?申明文件又有什么作用?以及怎样定义申明文件呢?

什么是申明文件

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

申明文件的作用

虽然 TypeScript 已经逐渐进入主流,但是市面上大部分库还是以 JavaScript 编写的,这个时候由于库没有像 TS 一样定义类型,因此需要一个声明文件来帮助库的使用者来获取库的类型提示。

说到这里小伙伴可能有疑问了,我们学习了接口、类、类型别名不都是用来定义类型的吗?怎么又会出来个申明文件呢?

接口、类、类型别名是用来定义类型别名的。但是每次都需要我们引入然后给变量定义类型。但是申明文件只需要我们申明一次,在项目中不需要再引入和定义类型就能全局直接使用。

我们来看个例子:

我们在new Vue的时候,如果不定义好Vue class是会报错的。并且每次我们new Vue的时候都需要引入class Vue

// type.js
interface VueOption {
  el: string;
  data: any;
}

export class Vue {
  options: VueOption;
  constructor(options: VueOption) {
    this.options = options;
  }
}

使用的时候我们必须import进来。

// main.js
import {Vue} from "./type.js"

const app = new Vue({
  el: "#app",
  data: {
    message: "hello world",
  },
});

我们改造下,使用申明文件

// index.d.ts
interface VueOption {
  el: string;
  data: any;
}

declare class Vue {
  options: VueOption;
  constructor(options: VueOption);
}

有了申明文件,项目全局不需要引入就能直接使用了。

// main.js
const app = new Vue({
  el: "#app",
  data: {
    message: "hello world",
  },
});

这样就不会报错了。

使用申明文件的好处是我们只需要定义一次就能全局使用。相对接口、类、类型别名来说是更方便的。

发布声明文件

我们为一个开源库编写了声明文件后应该如何发布?

目前有两个选择:

  1. 将什么文件向开源库提 PR,声明文件与源码放在一起,作为第一方声明。
  2. 发布到 DefinitelyTyped,作为第三方声明文件。

第一方声明

如果是手动写的声明文件,下面三种方式都能被正确的识别:

  1. 给 package.json 中的 types 或 typings 字段指定一个类型声明文件地址。

  2. 在项目根目录下,编写一个 index.d.ts 文件,该文件会被自动读取。

  3. 针对入口文件(package.json 中的 main 字段指定的入口文件),编写一个同名不同后缀的 .d.ts 文件,这样也会被自动读取。

第一种方式是给 package.json 中的 types 或 typings 字段指定一个类型声明文件地址。比如:

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

指定了 types 为 foo.d.ts 之后,导入此库的时候,就会去找 foo.d.ts 作为此库的类型声明文件了。

typings 与 types 一样,只是另一种写法。

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

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

比如 package.json 是这样时:

{
    "name": "foo",
    "version": "1.0.0",
    "main": "lib/index.js",
}

就会先识别 package.json 中是否存在 types 或 typings 字段。发现不存在,那么就会寻找是否存在 index.d.ts 文件。如果还是不存在,那么就会寻找是否存在 lib/index.d.ts 文件。假如说连 lib/index.d.ts 都不存在的话,就会被认为是一个没有提供类型声明文件的库了。

将声明文件发布到DefinitelyTyped

如果我们是在给别人的仓库添加类型声明文件,但原作者不愿意合并 pull request,那么就需要将声明文件发布到 @types 下。

与普通的 npm 模块不同,@types 是统一由 DefinitelyTyped 管理的。要将声明文件发布到 @types 下,就需要给 DefinitelyTyped 创建一个 pull-request,其中包含了类型声明文件,测试代码,以及 tsconfig.json 等。

pull-request 需要符合它们的规范,并且通过测试,才能被合并,稍后就会被自动发布到 @types 下。

介绍了申明文件的作用以及发布申明文件的两种方法,我们再来说说怎么写申明文件。

写申明文件

前面介绍的DefinitelyTyped,它定义了市面上主流的 JavaScript 库的 d.ts,我们可以在 Type Search里面搜索我们需要的申明文件,然后使用 npm 安装这些 d.ts

比如我们要安装 JQueryd.ts:

npm install @types/jquery -save

当然,如果使我们自己写的库,需要申明文件,这就需要我们手动来写申明文件了。写申明文件有两种方式。

自动生成

如果库的源码本身就是由 ts 写的,那么在使用 tsc 脚本将 ts 编译为 js 的时候,添加 declaration 选项,就可以同时也生成 .d.ts 声明文件了.

我们可以在命令行中添加 --declaration(简写 -d),或者在 tsconfig.json 中添加 declaration 选项。

这里以 tsconfig.json 为例:

{
    "compilerOptions": {
        "module": "commonjs",
        "outDir": "lib",
        "declaration": true,
    }
}

上例中我们添加了 outDir 选项,将 ts 文件的编译结果输出到 lib 目录下,然后添加了 declaration 选项,设置为 true,表示将会由 ts 文件自动生成同名的 .d.ts 声明文件,也会输出到 lib 目录下。

当然如果我们的库是js写的就没办法自动生成了,就只能手写了。

手动写

关键字 declare 表示声明的意思,我们可以用它来做出各种声明:

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

声明变量

declare var/let/const,全局变量的声明可以说是最简单的了,虽然 var/let/const 都可以使用的,但是通常情况下全局变量是不允许改动的,大多数情况下还是以 const 为主:

// src/jQuery.d.ts

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

声明函数

declare function 用来声明全局函数:

// src/jQuery.d.ts

declare function jQuery(selector: string): any;

声明类

declare class 用于声明全局类

// src/Person.d.ts

declare class Person {
  name: string;
  constructor(name: string);
  say(): string;
}

声明枚举

declare enum 是于声明全局枚举类型

// src/Directions.d.ts

declare enum Directions {
  Up,
  Down,
  Left,
  Right
}

声明命名空间

declare namespace,命名空间虽然在日常开发中已经不常见了,但是在 d.ts 文件编写时还是很常见的,它用来表示全局变量是一个对象,包含很多子属性。

比如 jQuery 是全局对象,而其包含一个 jQuery.ajax 用于处理 ajax 请求,这个时候命名空间就派上用场了:

// src/jQuery.d.ts

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

声明interface 和 type

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

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

// src/jQuery.d.ts

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

声明合并

假如 jQuery 既是一个函数,可以直接被调用 jQuery('#foo'),又是一个对象,拥有子属性 jQuery.ajax()(事实确实如此),那么我们可以组合多个声明语句,它们会不冲突的合并起来:

// src/jQuery.d.ts

declare function jQuery(selector: string): any;
declare namespace jQuery {
  function ajax(url: string, settings?: any): void;
}
// src/index.ts

jQuery('#foo');
jQuery.ajax('/api/get_something');

系列文章

TypeScript入门之环境搭建

TypeScript入门之数据类型

TypeScript入门之函数

TypeScript入门之接口

TypeScript入门之类

TypeScript入门之类型推断、类型断言、双重断言、非空断言、确定赋值断言、类型守卫、类型别名

TypeScript入门之泛型

TypeScript入门之装饰器

TypeScript入门之模块与命名空间

TypeScript入门之申明文件

TypeScript入门之常用内置工具类型

TypeScript入门之配置文件

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!