简介
申明文件在我们平时开发中用到的并不多,但是当我们要开源一个库的时候,就需要我们写申明文件了。
那什么是申明文件?申明文件又有什么作用?以及怎样定义申明文件呢?
什么是申明文件
通常我们会把声明语句放到一个单独的文件(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",
},
});
这样就不会报错了。
使用申明文件的好处是我们只需要定义一次就能全局使用。相对接口、类、类型别名来说是更方便的。
发布声明文件
我们为一个开源库编写了声明文件后应该如何发布?
目前有两个选择:
- 将什么文件向开源库提
PR
,声明文件与源码放在一起,作为第一方声明。 - 发布到
DefinitelyTyped
,作为第三方声明文件。
第一方声明
如果是手动写的声明文件,下面三种方式都能被正确的识别:
- 给
package.json
中的types
或typings
字段指定一个类型声明文件地址。 - 在项目根目录下,编写一个
index.d.ts
文件,该文件会被自动读取。 - 针对入口文件(
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
。
比如我们要安装 JQuery
的 d.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
除了全局变量之外,可能有一些类型我们也希望能暴露出来。
在类型声明文件中,我们可以直接使用 interface
或 type
来声明一个全局的接口或类型:
// 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入门之类型推断、类型断言、双重断言、非空断言、确定赋值断言、类型守卫、类型别名
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!