前言

关键字
declaration、ts-node、compilerOptions.typeRoots、lodash、

参考资料

  • compilerOptions.declaration链接
  • 「TypeScript 入门教程」声明文件:链接
  • lodash 官方文档:链接
  • 「简书」TS 的三斜线指令:链接
  • Triple-Slash Directives:链接

    notes

创建声明文件,可以是

  • 自动生成
  • 手动编写

在声明文件中不允许出现初始化表达式,也就是不能出现赋值语句。

自动生成

自动生成声明文件的前提

  1. 工程是使用 ts 来开发的
  2. 将配置文件 tsconfig.json 中的 compilerOptions.declaration 设置为 true

工程发布(编译)后,原来的 .ts 文件会变为 .js 文件。
原来写在 .ts 文件中的类型约束,在生成的发布结果中默认是不存在的。
但是如果满足上面提到的两个前提,那么在编译后会多生成一个 .d.ts 声明文件。
如果我们发布的文件需要被其他开发者使用,建议开启 compilerOptions.declaration,这么做能够更好地描述发布结果中的类型,让使用者清楚它们访问的成员的类型信息。

手动编写

🤔 什么情况下,需要我们手动编写声明文件?
答:没有提供声明文件的 js 库。

对于常用的库,它们一般都是具备类型声明文件的,这点我们无需担心。 常用的库,比如:axios、jquery、lodash、mock、moment 等等。

全局声明
声明一些全局的对象、属性、变量

全局变量的声明文件主要有以下几种语法:

  • declare var 声明全局变量
  • declare function 声明全局方法
  • declare class 声明全局类
  • declare enum 声明全局枚举类型
  • declare namespace 声明(含有子属性的)全局对象
  • interface 和 type 声明全局类型

模块声明

  • declare module 扩展模块

三斜线指令

/// <reference path="../../index.d.ts" />

三斜线指令是包含单个XML标签的单行注释。 注释的内容会做为编译器指令使用。 三斜线指令仅可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。

作用:在一个声明文件中,包含(引用)另一个声明文件。

codes

在学习 2. 在node中搭建TS开发环境 时,介绍如何搭建一个简单的 ts 开发环境。
使用npm i -D @types/node来安装 node 环境下的一些声明文件,现在我们尝试将 @types/node目录给删掉,尝试自己写声明文件。

image.png

  1. console.log('test');

当我们将 node 目录给删除后,node 的声明文件就没有了。
此时对 console 变量的类型声明信息也没了,所以我们在 index.ts 中书写 console.log('test')是会报错的。

下面尝试在全局的声明文件global.d.ts中添加 console 的声明信息。

  1. declare var console: {
  2. log(message?: any): void;
  3. };
  1. interface Console {
  2. log(message?: any): void;
  3. }
  4. declare var console: Console;
  1. declare namespace console {
  2. function log(message?: any): void;
  3. }

namespace表示命名空间,可以将其认为是一个对象,命名空间中的内容,必须通过命名空间.成员名访问。

添加 setTimeout、setInterval 的声明信息。

  1. declare namespace console {
  2. function log(message?: any): void;
  3. }
  4. type timeHandler = () => void;
  5. declare function setTimeout(handler: timeHandler, miliseconds: number): number;
  6. declare function setInterval(handler: timeHandler, miliseconds: number): number;
  1. console.log("test");
  2. setTimeout(() => {
  3. console.log('123');
  4. }, 1000);
  5. setInterval(() => {
  6. console.log('abc');
  7. }, 1000);

当我们在全局声明文件中添加上 consolesetTimeoutsetInterval的类型信息后,再 index.ts 中访问它们,就不会报错了。

  1. const _ = require("lodash");
  2. console.log(_.chunk); // => [Function: chunk]
  1. import * as _ from "lodash"; // ×
  2. // => Could not find a declaration file for module 'lodash'.

npm i lodash
先提前安装 lodash

require("lodash")
注意此时只能通过 node 模块化标准 commonjs 模块化写法来导入 lodash
由于咋们安装的 lodash 库,它是使用 js 写的,仅通过上述方式安装,在 .ts 文件中书写代码时没法给我们提供智能提示。

import * as _ from "lodash"
如果使用 es module 模块化写法来导入 lodash,会报错:无法找到 lodash 的声明文件。

🤔 如何安装 lodash 的声明文件?
答:npm i @types/lodash

image.png

对于常见的库 xxx,我们都可以通过安装 @types/xxx 来获取到它的声明文件
但是对于一些没有提供声明文件的库,或者是我们自己写的库,那我们就得手动编写声明文件,下面介绍模块化声明该如何写。

  1. // 模块声明文件
  2. declare module "lodash" {
  3. export function chunk(arr: any[], size: number): any[][];
  4. }
  1. import * as _ from "lodash";
  2. console.log(_.chunk(["a", "b", "c", "d"], 2));
  3. // => [['a', 'b'], ['c', 'd']]
  1. {
  2. "devDependencies": {
  3. "@types/node": "^17.0.23"
  4. },
  5. "scripts": {
  6. "dev": "nodemon --watch src -e ts --exec ts-node ./src/index.ts",
  7. "build": "rm -rf dist && tsc"
  8. },
  9. "dependencies": {
  10. "lodash": "^4.17.21"
  11. }
  12. }

export function chunk(arr: any[], size: number): any[][] 可以使用泛型进行优化 export function chunk<T>(arr: T[], size: number): T[][]

lodash.d.ts 注意声明文件的命名,如果命名为 index.d.ts,那么在 index.ts 中导入 lodash 时依旧是会报错的。

这涉及到 node 中文件的查找逻辑,和模块的查找逻辑类似 ./src/index.d.ts./src/index.ts./src/lodash.d.ts./src/index.ts./src/lodash/index.d.ts./src/index.ts

npm run build 先编译,然后运行编译结果 index.js 文件 node ./dist/index.js(在项目的根目录执行该命令)

  1. Object.defineProperty(exports, "__esModule", {
  2. value: true
  3. });
  4. const _ = require("lodash");
  5. console.log(_.chunk(["a", "b", "c", "d"], 2));

打印结果为 [ [ 'a', 'b' ], [ 'c', 'd' ] ] 和官方一致。官方 example

🤔 如果直接执行 **npm run dev**,运行 **index.ts**,结果会如何?
答:会报错。

image.png

  1. {
  2. "compilerOptions": {
  3. "target": "es2016",
  4. "module": "commonjs",
  5. "lib": [
  6. "es2016"
  7. ],
  8. "outDir": "./dist",
  9. "strictNullChecks": true,
  10. // "strictPropertyInitialization": true,
  11. "removeComments": true,
  12. "noImplicitUseStrict": true,
  13. "noImplicitAny": true,
  14. "noImplicitThis": true,
  15. "experimentalDecorators": true,
  16. "emitDecoratorMetadata": true
  17. },
  18. "include": [
  19. "./src"
  20. ],
  21. }

这是由 ts-node 这个库决定的,细节 👉 链接 image.png 简述报错原因:无法找到对应的类型声明文件。 ts-node 需要我们告诉它,声明文件的具体位置。因为 ts-node 在默认情况下,不会去读取 tsconfig.json 中的配置:files、include、exclude。所以它无法得知我们所写的声明文件所在的位置。 如果 ts-node 会读 include 配置的内容,就知道声明文件的位置了,自然就不会报错了。从上述代码看来,我们可以得知声明文件是在 include 中的。

在了解到报错原因后,根据 ts-node 官方文档的提示,我们可以通过 compilerOptions.typeRoots 这个配置来告知执行的文件,我们将类型声明文件都放在哪里了。

  1. {
  2. "compilerOptions": {
  3. "target": "es2016",
  4. "module": "commonjs",
  5. "lib": [
  6. "es2016"
  7. ],
  8. "outDir": "./dist",
  9. "strictNullChecks": true,
  10. // "strictPropertyInitialization": true,
  11. "removeComments": true,
  12. "noImplicitUseStrict": true,
  13. "noImplicitAny": true,
  14. "noImplicitThis": true,
  15. "experimentalDecorators": true,
  16. "emitDecoratorMetadata": true,
  17. "declaration": true,
  18. "typeRoots": [
  19. "./node_modules/@types",
  20. "./src/types",
  21. ]
  22. },
  23. "include": [
  24. "./src"
  25. ],
  26. }

下面介绍三斜线指令。(在测试这个指令时,有报错,所以就按部就班地按照袁老的讲解,记录一下它的用法)
image.png

变化前的位置:./src/types/lodash/index.d.ts
变化后的位置:./index.d.ts

根据我们在 tsconfig.json 中的配置,会到 ./src/types 里面查找声明文件。
但是咋们将其位置丢到的根目录下,所以找不到它,但是依旧可以找到 ./src/types 目录下的 global.d.ts 文件。

三斜线指令的作用:在一个声明文件中,包含(引用)另一个声明文件。
在声明文件 ./src/types/global.d.ts 中,引用 ./index.d.ts声明文件,这样就能找到原来的 ./src/types/lodash/index.d.ts 文件了。

  1. /// <reference path="../../index.d.ts" />
  2. // declare var console: {
  3. // log(message?: any): void;
  4. // };
  5. // type timeHandler = () => void;
  6. // declare function setTimeout(handler: timeHandler, miliseconds: number): number;
  7. // declare function setInterval(handler: timeHandler, miliseconds: number): number;

/// <reference path="../../index.d.ts" /> 从当前声明文件的相对位置去查找 ../../index.d.ts 文件。