当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
-
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 />
三斜线指令
通常我们会把声明语句放到一个单独的文件(jQuery.d.ts
)中,这就是声明文件
声明文件必需以 .d.ts 为后缀。
全局变量这种模式的声明文件
// src/jQuery.d.ts
declare var jQuery: (selector: string) => any;
将jQuery.d.ts
放在项目目录中,可检查tsconfig.json
中的 files
、include
和 exclude
配置,确保其包含了jQuery.d.ts
文件
第三方声明文件
使用 @types 统一管理第三方库的声明文件
npm install @types/jquery --save-dev
书写声明文件
当一个第三方库没有提供声明文件时,我们就需要自己书写声明文件了。前面只介绍了最简单的声明文件内容,而真正书写一个声明文件并不是一件简单的事,以下会详细介绍如何书写声明文件。
全局变量
如jQuery
declare var
声明全局变量
declare let jQuery: (selector: string) => any;
declare const jQuery: (selector: string) => any;
declare function
声明全局变量
declare function jQuery(selector: string): any;
declare class
declare class Animal {
name: string;
constructor(name: string);
sayHi(): string;
}
declare enum
declare enum Directions {
Up,
Down,
Left,
Right
}
declare namespace
declare namespace jQuery {
function ajax(url: string, settings?: any): void;
}
嵌套的命名空间
declare namespace jQuery {
function ajax(url: string, settings?: any): void;
namespace fn {
function extend(object: any): void;
}
}
interface
和 type
interface AjaxSettings {
method?: 'GET' | 'POST'
data?: any;
}
declare namespace jQuery {
function ajax(url: string, settings?: AjaxSettings): void;
}
为防止全局变量污染,最好是放在namespace
下
declare namespace jQuery {
interface AjaxSettings {
method?: 'GET' | 'POST'
data?: any;
}
function ajax(url: string, settings?: AjaxSettings): void;
}
// 使用
let settings: jQuery.AjaxSettings = {
method: 'POST',
data: {
name: 'foo'
}
};
jQuery.ajax('/api/post_something', settings);
npm包
npm 包的声明文件可能存在于两个地方:
- 与该 npm 包绑定在一起。判断依据是 package.json 中有 types 字段,或者有一个 index.d.ts 声明文件。这种模式不需要额外安装其他包,是最为推荐的,所以以后我们自己创建 npm 包的时候,最好也将声明文件与 npm 包绑定在一起。
- 发布到 @types 里。我们只需要尝试安装一下对应的 @types 包就知道是否存在该声明文件,安装命令是 npm install @types/foo --save-dev。这种模式一般是由于 npm 包的维护者没有提供声明文件,所以只能由其他人将声明文件发布到 @types 里了。
假如以上两种方式都没有找到对应的声明文件,那么我们就需要自己为它写声明文件了。由于是通过 import 语句导入的模块,所以声明文件存放的位置也有所约束,一般有两种方案:
- 创建一个 node_modules/@types/foo/index.d.ts 文件,存放 foo 模块的声明文件。这种方式不需要额外的配置,但是 node_modules 目录不稳定,代码也没有被保存到仓库中,无法回溯版本,有不小心被删除的风险,故不太建议用这种方案,一般只用作临时测试。
- 创建一个 types 目录,专门用来管理自己写的声明文件,将 foo 的声明文件放到 types/foo/index.d.ts 中。这种方式需要配置下 tsconfig.json 中的 paths 和 baseUrl 字段。
export
npm 包的声明文件与全局变量的声明文件有很大区别。在 npm 包的声明文件中,使用 declare
不再会声明一个全局变量,而只会在当前文件中声明一个局部变量。只有在声明文件中使用export
导出,然后在使用方import
导入后,才会应用到这些类型声明。
混用 declare
和export
// types/foo/index.d.ts
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 };
注意,与全局变量的声明文件类似,interface 前是不需要 declare 的。
export namespace
// types/foo/index.d.ts
export namespace foo {
const name: string;
namespace bar {
function baz(): string;
}
}
// src/index.ts
import { foo } from 'foo';
console.log(foo.name);
foo.bar.baz();
export default
// types/foo/index.d.ts
export default function foo(): string;
// src/index.ts
import foo from 'foo';
foo();
注意,只有 function
、class
和 interface
可以直接默认导出,其他的变量需要先定义出来,再默认导出
export =
在 commonjs 规范中,我们用以下方式来导出一个模块:
// types/foo/index.d.ts
export = foo;
declare function foo(): string;
declare namespace foo {
const bar: number;
}
UMD 库
// types/foo/index.d.ts
export as namespace foo;
export default foo;
declare function foo(): string;
declare namespace foo {
const bar: number;
}
直接扩展全局变量
有的第三方库扩展了一个全局变量,可是此全局变量的类型却没有相应的更新过来,就会导致 ts 编译错误,此时就需要扩展全局变量的类型。比如扩展 String 类型
interface String {
prependHello(): string;
}
'foo'.prependHello();
在 npm 包或 UMD 库中扩展全局变量
// types/foo/index.d.ts
declare global {
interface String {
prependHello(): string;
}
}
export {};
// src/index.ts
'bar'.prependHello();
模块插件
// types/moment-plugin/index.d.ts
import * as moment from 'moment';
declare module 'moment' {
export function foo(): moment.CalendarKey;
}
// src/index.ts
import * as moment from 'moment';
import 'moment-plugin';
moment.foo();
声明文件中的依赖
// types/moment-plugin/index.d.ts
import * as moment from 'moment';
declare module 'moment' {
export function foo(): moment.CalendarKey;
}
除了可以在声明文件中通过 import 导入另一个声明文件中的类型之外,还有一个语法也可以用来导入另一个声明文件,那就是三斜线指令。
不建议使用,当在以下几个场景下,我们才需要使用三斜线指令替代 import:
- 当我们在书写一个全局变量的声明文件时
- 当我们需要依赖一个全局变量的声明文件时
// types/jquery-plugin/index.d.ts
/// <reference types="jquery" />
declare function foo(options: JQuery.AjaxSettings): string;
注意,三斜线指令必须放在文件的最顶端,三斜线指令的前面只允许出现单行或多行注释。
自动生成声明文件
如果库的源码本身就是由 ts 写的,那么在使用 tsc 脚本将 ts 编译为 js 的时候,添加 declaration 选项,就可以同时也生成 .d.ts 声明文件了。
tsc test.ts -d
发布声明文件
此时有两种方案:
- 将声明文件和源码放在一起
- 将声明文件发布到 @types 下
这两种方案中优先选择第一种方案。保持声明文件与源码在一起,使用时就不需要额外增加单独的声明文件库的依赖了,而且也能保证声明文件的版本与源码的版本保持一致。