前言
关键字declaration
、ts-node、compilerOptions.typeRoots
、lodash、
参考资料
compilerOptions.declaration
:链接- 「TypeScript 入门教程」声明文件:链接
- lodash 官方文档:链接
- 「简书」TS 的三斜线指令:链接
- Triple-Slash Directives:链接
notes
创建声明文件,可以是
- 自动生成
- 手动编写
在声明文件中不允许出现初始化表达式,也就是不能出现赋值语句。
自动生成
自动生成声明文件的前提
- 工程是使用 ts 来开发的
- 将配置文件
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
目录给删掉,尝试自己写声明文件。
console.log('test');
当我们将 node 目录给删除后,node 的声明文件就没有了。
此时对 console 变量的类型声明信息也没了,所以我们在 index.ts 中书写 console.log('test')
是会报错的。
下面尝试在全局的声明文件global.d.ts
中添加 console
的声明信息。
declare var console: {
log(message?: any): void;
};
interface Console {
log(message?: any): void;
}
declare var console: Console;
declare namespace console {
function log(message?: any): void;
}
namespace
表示命名空间,可以将其认为是一个对象,命名空间中的内容,必须通过命名空间.成员名
访问。
添加 setTimeout、setInterval 的声明信息。
declare namespace console {
function log(message?: any): void;
}
type timeHandler = () => void;
declare function setTimeout(handler: timeHandler, miliseconds: number): number;
declare function setInterval(handler: timeHandler, miliseconds: number): number;
console.log("test");
setTimeout(() => {
console.log('123');
}, 1000);
setInterval(() => {
console.log('abc');
}, 1000);
当我们在全局声明文件中添加上 console
、setTimeout
、setInterval
的类型信息后,再 index.ts 中访问它们,就不会报错了。
const _ = require("lodash");
console.log(_.chunk); // => [Function: chunk]
import * as _ from "lodash"; // ×
// => 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
对于常见的库 xxx,我们都可以通过安装 @types/xxx
来获取到它的声明文件
但是对于一些没有提供声明文件的库,或者是我们自己写的库,那我们就得手动编写声明文件,下面介绍模块化声明该如何写。
// 模块声明文件
declare module "lodash" {
export function chunk(arr: any[], size: number): any[][];
}
import * as _ from "lodash";
console.log(_.chunk(["a", "b", "c", "d"], 2));
// => [['a', 'b'], ['c', 'd']]
{
"devDependencies": {
"@types/node": "^17.0.23"
},
"scripts": {
"dev": "nodemon --watch src -e ts --exec ts-node ./src/index.ts",
"build": "rm -rf dist && tsc"
},
"dependencies": {
"lodash": "^4.17.21"
}
}
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
(在项目的根目录执行该命令)
Object.defineProperty(exports, "__esModule", {
value: true
});
const _ = require("lodash");
console.log(_.chunk(["a", "b", "c", "d"], 2));
打印结果为 [ [ 'a', 'b' ], [ 'c', 'd' ] ]
和官方一致。官方 example
🤔 如果直接执行 **npm run dev**
,运行 **index.ts**
,结果会如何?
答:会报错。
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"lib": [
"es2016"
],
"outDir": "./dist",
"strictNullChecks": true,
// "strictPropertyInitialization": true,
"removeComments": true,
"noImplicitUseStrict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": [
"./src"
],
}
这是由 ts-node 这个库决定的,细节 👉 链接
简述报错原因:无法找到对应的类型声明文件。 ts-node 需要我们告诉它,声明文件的具体位置。因为 ts-node 在默认情况下,不会去读取 tsconfig.json 中的配置:files、include、exclude。所以它无法得知我们所写的声明文件所在的位置。 如果 ts-node 会读 include 配置的内容,就知道声明文件的位置了,自然就不会报错了。从上述代码看来,我们可以得知声明文件是在 include 中的。
在了解到报错原因后,根据 ts-node 官方文档的提示,我们可以通过 compilerOptions.typeRoots
这个配置来告知执行的文件,我们将类型声明文件都放在哪里了。
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"lib": [
"es2016"
],
"outDir": "./dist",
"strictNullChecks": true,
// "strictPropertyInitialization": true,
"removeComments": true,
"noImplicitUseStrict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": true,
"typeRoots": [
"./node_modules/@types",
"./src/types",
]
},
"include": [
"./src"
],
}
下面介绍三斜线指令。(在测试这个指令时,有报错,所以就按部就班地按照袁老的讲解,记录一下它的用法)
变化前的位置:./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
文件了。
/// <reference path="../../index.d.ts" />
// declare var console: {
// log(message?: any): void;
// };
// type timeHandler = () => void;
// declare function setTimeout(handler: timeHandler, miliseconds: number): number;
// declare function setInterval(handler: timeHandler, miliseconds: number): number;
/// <reference path="../../index.d.ts" />
从当前声明文件的相对位置去查找 ../../index.d.ts
文件。