声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
背景
声明文件是形如
xx.d.ts的文件类型;有些第三方库需要并没有编写好声明文件,需要自己去获取;如果是流行的库,可能网上有别人(社区)写好的声明文件,可以尝试下载,声明文件一般为
@type/库名;如果是不活跃的库,可能需要我们手动去声明;例如:为 express 手写一个声明库(见示例)
书写声明文件
在不同的场景下,声明文件的内容和使用方式会有所区别。
库的使用场景主要有以下几种:
- 全局变量:通过
<script>标签引入第三方库,注入全局变量 - npm 包:通过
import foo from 'foo'导入,符合 ES6 模块规范 - UMD 库:既可以通过
<script>标签引入,又可以通过import导入 - 直接扩展全局变量:通过
<script>标签引入后,改变一个全局变量的结构 - 在 npm 包或 UMD 库中扩展全局变量:引用 npm 包或 UMD 库后,改变一个全局变量的结构
- 模块插件:通过
<script>或import导入后,改变另一个模块的结构
全局变量
注意:所有声明语句中只能定义类型,切勿在声明语句中定义具体的实现。
declare var
用来定义一个全局变量的类型。
// src/jQuery.d.ts declare let jQuery: (selector: string) => any; // src/index.ts jQuery("#foo"); // 使用 declare let 定义的 jQuery 类型,允许修改这个全局变量 jQuery = function (selector) { return document.querySelector(selector); };declare function
用来定义全局函数的类型。Query 其实就是一个函数,所以也可以用
function来定义:// src/jQuery.d.ts declare function jQuery(selector: string): any; // src/index.ts jQuery("#foo");declare class
当全局变量是一个类的时候,我们用
declare class来定义它的类型:// src/Animal.d.ts declare class Animal { name: string; constructor(name: string); sayHi(): string; } // src/index.ts let cat = new Animal("Tom");declare enum
使用
declare enum定义的枚举类型也称作外部枚举(Ambient Enums)。// src/Directions.d.ts declare enum Directions { Up, Down, Left, Right, } // src/index.ts let directions = [ Directions.Up, Directions.Down, Directions.Left, Directions.Right, ];declare namespace(淘汰)
嵌套的命名空间(*)
interface 和 type
除了全局变量之外,可能有一些类型我们也希望能暴露出来。在类型声明文件中,我们可以直接使用
interface或type来声明一个全局的接口或类型12:// src/jQuery.d.ts interface AjaxSettings { method?: "GET" | "POST"; data?: any; } declare namespace jQuery { function ajax(url: string, settings?: AjaxSettings): void; }这样的话,在其他文件中也可以使用这个接口或类型了:
// src/index.ts let settings: AjaxSettings = { method: "POST", data: { name: "foo", }, }; jQuery.ajax("/api/post_something", settings);type与interface类似。防止命名冲突
暴露在最外层的
interface或type会作为全局类型作用于整个项目中,我们应该尽可能的减少全局变量或全局类型的数量。故最好将他们放到namespace声明合并
UMD 库
既可以通过 <script> 标签引入,又可以通过 import 导入的库,称为 UMD 库。相比于 npm 包的类型声明文件,我们需要额外声明一个全局变量,为了实现这种方式,ts 提供了一个新语法 export as namespace。
示例
为 express 手写一个声明库
// index.ts import express from "express"; const app = express(); const router = express.Router(); app.use("/api", router); router.get("/api", (req: any, res: any) => { res.json({ code: 200, }); }); app.listen(9002, () => { console.log("9002"); });// express.d.ts declare module "express" { interface Router { get(path: string, cb: (req: any, res: any) => void): void; } interface App { use(path: string, router: any): void; listen(port: number, cb?: () => void); } interface Express { (): App; Router(): Router; } const express: Express; export default express; }