首先,需要识别已有的 JS 库的类型。
在 TS 出现之前,大部分 JS 库都是纯 JS 库;有了 TS 之后,一些库开始用 TS 编写,但是编译完成之后它们还是 JS 代码。
如果我们要在 TS 中使用一些早期的库,这些库都没有使用 TS 编写的情况下,就会有一些类型的问题。所以,几乎所有的 JS 库,都有社区为它们编写的声明文件。我们在使用的时候直接安装这些声明文件就可以了。
但是有一些库可能比较冷门,并没有社区为它们编写声明文件,所以我们就需要学习如何自己为这些库编写声明文件,从而在我们的 TS 中更好的引用。
1、识别已有的 JS 库的类型
(1)全局库
在早期,那时候还没有 webpack
这些编译工具,也没有这些模块化的概念。都是直接在html
页面中直接使用 script
标签来引入 js 文件,之后就可以在其后面使用引入的库了。例如:jquery
<html>
<head></head>
<body>
<script src="xxx/jquery.min.js"></script>
</body>
</html>
umd即可以作为模块使用,又可以作为全局库使用。
如果编译出来的代码,有一些顶级的 var 语句,或者 function 声明,则它可能是全局库
或者有一个或多个赋值给 window
的赋值语句,则它可能是全局库
我们来编写一个全局库
// modules/handle-title.js
function setTitle(title) {
document && (document.title = title);
}
function getTitle() {
return document ? document.title : "";
}
let documentTitle = getTitle();
引入这个全局库
// template/index.html
<html>
<head></head>
<body>
<script src="../modules/handle-title.js"></script>
</body>
</html>
之后会发现,运行起来之后是找不到它的
这是因为编译之后并没有把文件引入进来
我们这里可以简单的使用 webpack
插件直接把它拷贝过去
npm i -D copy-webpack-plugin@5
// build/webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path')
module.exports = {
...
plugins: [
...
new CopyWebpackPlugin([{
from: path.join(__dirname, '../src/modules/handle-title.js'),
to: path.resolve(__dirname, '../dist')
}])
]
}
同时修改 index.html
模板,将引入路径改为相对路径:
// template/index.html
<script src="./handle-title.js"></script>
此时再进行打包:npm run build
打包完成后即可看到 handle-title.js
文件已经被复制到 dist
文件夹下了
此时可以在浏览器控制台看到效果:
我们现在试一下在其他文件中使用全局库的方法:
- 首先在主文件引入
// src/index.ts
import './modules/index';
- 在
modules/index
文件中使用
console.log(documentTitle);
- 此时可以看到,控制台会打印出
documentTitle
的值,但是也会报错,这是因为我们需要给它写声明文件
// src/globals.d.ts
declare function setTitle(title: string | number): void;
declare function getTitle(): string;
declare let documentTitle: string;
- 编辑
tsconfig.json
{
"compilerOptions": {...},
"include": [
"./src/**/*.ts",
"./src/**/*.d.ts"
]
}
如果不编写 include
,则默认会把项目中的所有 .d.ts
或者 .ts
文件都引入进来。指定了的话,就之后加载这里面定义了的文件
此时可以看到没有报错信息了!!!
(2)模块化库
模块化库也就是依赖了模块解析器的库。
后面的章节介绍模块化库,这里暂时不做详细解释...
(3)UMD库
UMD库将全局库和模块库的功能进行了合并,它会首先判断环境中是否有模块加载器的一些特定方法,例如:
typeof define === function
:说明是有define
方法的typeof module === 'object' && module.export
:说明是有模块加载系统的- 如果上面两种都没有,则走全局的加载形式
现在很多库都是UMD库,它即可以在 html
文件里以 script
形式来引入,也可以使用模块系统来加载。
2、处理库的声明文件
我们之前讲过,基本上所有的库都有社区为它们编写声明文件
比如我们现在随便找一个 node_modules
中的库: is-buffer
可以看到它里面只有 js 文件,没有声明文件,如果我们的 TS 项目要使用到里面方法的话是会报错的。
npm i -D @types/is-buffer
这条命令如果安装成功了,说明有这个库的声明文件;
如果没有安装成功,说明没有社区为它编写声明文件,那我们就需要自己写了。
在 node_modules/@types/is-buffer/index.d.ts
中可以看到这个库的声明文件内容
(1)模块插件或UMD插件
一些模块是支持插件机制的,例如 jquery 的插件就非常多。
我们可以为库书写声明文件的同时,为库的插件也定义声明文件,可以参考文档 中的 moduel-plugin.d.ts
文件。
(2)修改全局的模块
有些插件是影响全局的全局插件:比如说有些库会在原生的 js 数据类型的原型对象上添加一些方法,使之拥有更为丰富的方法,
String.prototype.getFirstLetter = function () {
return this[0];
}
声明文件:
// globals.d.ts
interface String {
getFirstLetter(): string
}
(3)使用依赖
一个库多数会依赖其他库。比如 nodeJS 的一些库会依赖与 node 内置的一些模块:例如 fs、path 等。
所以,可以在定义库声明文件的时候,声明对其他库的依赖,从而加载其他库的声明。
- 如果是依赖全局的库
可以使用三斜线 /// reference
(声明文件中) 通过 types 这个属性来指定加载了某个全局库
///<reference types="moment" />
这里就会引入全局库 moment
的声明文件。
- 如果我们依赖的是模块库
可以使用 import
语句(声明文件中)
import * as moment from 'moment'
3、防止命名冲突
我们在写全局声明的时候,在全局范围内定义大量类型,有时候会导致命名冲突。
所以建议相关的定义放在命名空间里。
4、快捷外部模块声明
如果我们使用一个新模块,不想花时间精力为这个模块写声明,就可以使用快捷外部模块声明。
比如我们使用 moment
模块,可以在 typings
文件夹下新建 moment
文件夹,在其中新建 index.d.ts
文件:
declare module 'moment'