TS从入门到放弃【二十】:声明文件

1,665 阅读5分钟

首先,需要识别已有的 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>

之后会发现,运行起来之后是找不到它的

ts-6.png

这是因为编译之后并没有把文件引入进来

我们这里可以简单的使用 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 文件夹下了

此时可以在浏览器控制台看到效果:

ts-7.png

我们现在试一下在其他文件中使用全局库的方法:

  • 首先在主文件引入
// 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'