当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。这是因为前端第三方库大多都是非 TypeScript 库,基本上都是使用 JS 编写的,在 TS 中使用非 TS 编写的第三方库,需要有个 xx.d.ts 声明文件。
声明文件一般来讲不需要自己写,有两种情况来得到声明文件:
-
npm
下载第三方包的时候,人家node_modules 里面自带了声明文件 index.d.ts 。例如:dayjs 和 moment 等等。 -
npm
下载第三方包的时候,没有 TS 的声明文件,这时候可以通过 https://microsoft.github.io/TypeSearch/ 网址进行搜索。然后 npm 安装,一般依赖的格式为@types/xxxx
。例如:request,sha1,jQuery,lodash等等。
声明文件的新语法有,点击可查看对应的讲解:
-
declare var
声明全局变量 -
declare function
声明全局方法 -
declare class
声明全局类 -
declare enum
声明全局枚举类型 -
declare namespace
声明(含有子属性的)全局对象 -
interface
和type
声明全局类型 -
export
导出变量 -
export namespace
导出(含有子属性的)对象 -
export default
ES6 默认导出 -
export =
commonjs 导出模块 -
export as namespace
UMD 库声明全局变量,UMD 固定语法。 -
declare global
扩展全局变量 -
declare module
扩展模块 -
/// <reference />
三斜线指令
一、认识声明文件
在 index.html 引入编译后的 JS 文件 index.js 和一个纯 Javascript 写的第三方库。
<script src="./three.js"></script>
<script src="./js/index.js"></script>
现在需要在假设 three.js 里面的内容是:
var a = 10;
让我们在 index.ts 文件里面读取到这个变量,但是在 TS 里面不认识 JS 的语法,所以的写个声明文件来声明下全局变量 a ,来让 TS 认识。我们就可以在第三方文件 three.js 同级目录下新建一个声明文件 three.d.ts 文件。
declare var a:number;
这时在 index.ts 里面使用:
console.log(a);//10
可以得到 three.js 文件里面全局变量 a 的值。现在来看其实就是 JavaScript 写的东西,在 Typescript 中无法直接使用,xxx.d.ts 就作为 JS 和 TS 之间,沟通的桥梁。一般来讲声明文件就是对 Javascript 进行类型美化。
二、声明文件类型
在不同的场景下,我们引入第三方库的方式是不同的,所以声明文件的内容和使用方式会有所区别。
使用第三方库时,常见的声明类型主要有以下几种:
- 全局变量:通过
<script>
标签引入第三方库,注入全局变量 - npm 包:通过
import foo from 'foo'
导入,符合 ES6 模块规范 - UMD 库:既可以通过
<script>
标签引入,又可以通过import
导入 - 直接扩展全局变量:通过
<script>
标签引入后,改变一个全局变量的结构 - 在 npm 包或 UMD 库中扩展全局变量:引用 npm 包或 UMD 库后,改变一个全局变量的结构
- 模块插件:通过
<script>
或import
导入后,改变另一个模块的结构
常见之中最最常见的就是前三种了,下面也就只介绍这三个,其他的自己看文档。
三、全局变量
全局变量是最简单的一种场景,最常见的就是通过 <script> 标签引入 jQuery,注入全局变量 $ 和 jQuery。
使用全局变量的声明文件存放的位置?
声明文件如果是以 npm install @types/xxx --save-dev 安装的,则不需要任何配置。如果不是,则建议将声明文件直接存放于当前项目中。
全局变量的声明文件主要有以下几种语法:
-
declare var
声明全局变量 -
declare function
声明全局方法 -
declare class
声明全局类 -
declare enum
声明全局枚举类型 -
declare namespace
声明(含有子属性的)全局对象 -
interface
和type
声明全局类型
举个例子:
在 index.html 中我们依然引入了两个 JS 文件:
<script src="./globalFunc.js"></script>
<script src="./js/index.js"></script>
其中 globalFunc 文件作为第三方库使用,其内容为:
function globalFunc(params){
console.log(params);
};
globalFunc.version = "v1.0.0";
globalFunc.dosomething = function(){
console.log("I am globalFunc dosomething");
};
我们现在需要在 index.ts 文件里面使用 globalFunc.js 文件里面的东西,很明显不能直接使用的,强行使用也不是不行,就是在写的代码的时候没有类型检查在 TS 编辑器里面会有错误提示,所以得写声明文件,声明文件就在 globalFunc 同级目录下,命名为 globalFunc.d.ts 文件。
declare function globalFunc(params:globalFunc.params):void
//类型兼容性
declare namespace globalFunc{
const version : string;
interface params{
[key:string]:any
}
function dosomething():void;
}
这时候在 index.ts 文件里面使用就啥问题都没有了。注意虽然 namespace 被淘汰了,但是在声明文件中,declare namespace 还是比较常用的,它用来表示全局变量是一个对象,包含很多子属性。
四、npm 模块
一般我们通过 import foo from 'foo'
导入一个 npm 包,这是符合 ES6 模块规范的。
在我们尝试给一个 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 字段。
tsconfig.json 中的 paths 和 baseUrl 字段例子:查找 tsconfig.json 所在目录下 types 里面的所有文件。
{
"compilerOptions": {
"module": "commonjs",
"baseUrl": "./",
"paths": {
"*": ["types/*"]
}
}
}
const version = "v1.1.1";
const methods = function(){};
function moduleLib(){};
moduleLib.version = version;
moduleLib.methods = methods;
module.exports = moduleLib;
declare function moduleLib(params:globalFunc.params):void
declare namespace globalFunc{
const version : string;
interface params{
[key:string]:any
}
function dosomething():void;
}
export = moduleLib;
import moduleLib from "./moduleLib";
console.log(moduleLib);
五、在 npm 包或 UMD 库中扩展全局变量
在第三小节我们声明了一个全局函数 globalFunc
现在我要在 index.ts 对其进行扩展,来新增一个方法,代码如下:
globalFunc.add = ()=>{};
//报错如下
//Property 'add' does not exist on type 'typeof globalFunc'.
现在添加声明代码:
declare global {
namespace globalFunc{
function add() :void
}
}
globalFunc.add = ()=>{};
新增的报错如下:
Augmentations for the global scope can only be directly nested in external modules or ambient module declarations.
全局作用域的扩展只能直接嵌套在外部模块或环境模块声明中
可以理解为 declare global
直接作用在模块中,也就是必须出现 import 或者 export 这些文件里面,这个文件才不会被当成一个全局的 TS 脚本,而是模块。所以,我们可以导出一个空对象,用来告诉编译器这是一个模块的声明文件。
// declare global 扩展全局变量
export {}
declare global {
namespace globalFunc{
function add() :void
}
}
globalFunc.add = ()=>{};
console.log(globalFunc.add());
六、模块插件
有时通过 import
导入一个模块插件,如果我们想要扩展此插件。ts 提供了一个语法 declare module
,它可以用来扩展原有模块的类型。
现在我要给 dayjs 这个模块添加一个方法,名字就叫做 getDate :
import day from "dayjs";
//千万不要忘记了引号
declare module "dayjs" {
export function getDate():number;
}
day.getDate = function(){
const date = new Date();
return Number(date);
};
console.log(day().format());
console.log(day.getDate());
//2020-02-09T14:10:34+08:00
//1581228634901
通过 declare module 可以直接扩展第三方模块。
七、三斜线指令
和 namespace
用来代替 JavaScript 进行模块开发类似,三斜线指令也是 ts 在早期版本中为了描述模块之间的依赖关系而创造的语法。随着 ES6 的广泛应用,现在已经不建议再使用 ts 中的三斜线指令来声明模块之间的依赖关系了。
但是在声明文件中,它还是有一定的用武之地。
类似于声明文件中的 import
,它可以用来导入另一个声明文件。与 import
的区别是,如果想使用另一个文件,而这个文件不支持 import 语法就要使用三斜线语法,例如 Jquery 的声明文件
// node_modules/@types/jquery/index.d.ts
/// <reference types="sizzle" />
/// <reference path="JQueryStatic.d.ts" />
/// <reference path="JQuery.d.ts" />
/// <reference path="misc.d.ts" />
/// <reference path="legacy.d.ts" />
export = jQuery;
Jquery 的声明文件是从其他文件里面引入的,这些 xx.d.ts 就是简单的 TS 文件肯定时不支持模块引入的,我们想要使用就只能使用三斜线了,特别类似 <script src="xxx.js"></script>
。
其中用到了 types 和 path 两种不同的指令。它们的区别是:types 用于声明对另一个库的依赖,而 path 用于声明对另一个文件的依赖。sizzle 这个第三方库里面肯定是有一个 index.d.ts 的,所以本质上两个三斜线命令都是为了引入 xx.d.ts 。
如何自动生成声明文件?
在 tsconfig.json 里面进行配置:
- declaration 为每个 TS 文件生成 xx.d.ts 和 x.js 文件。
- declarationDir 设置生成 .d.ts 文件的目录
- declarationMap 对每个 .d.ts 文件,都生成对应的 .d.ts.map(sourcemap)文件
- emitDeclarationOnly 仅生成 .d.ts 文件,不生成 .js 文件