配置名称 | 含义 |
---|---|
module | 设置编译结果中使用的模块化标准 |
moduleResolution | 设置解析模块的模式 |
noImplicitUseStrict | 编译结果中不包含”use strict” |
removeComments | 编译结果移除注释 |
noEmitOnError | 错误时不生成编译结果 |
esModuleInterop | 启用 es 模块化交互非 es 模块导出 |
本章节开始介绍 ts 的模块化,在介绍模块的过程中,会涉及到一些模块化的相关配置,相关配置字段都罗列在这张表中了。
5-1. 在 TS 中使用模块化
前言
任务列表
- 掌握 ts 中如何书写模块化语句;
notes
前端领域的模块化标准
- es6
- commonjs
- amd
- cmd
- umd
- system
- esnext
Best Practice
TS 中,导入和导出模块,推荐统一使用 es6 的模块化标准。
:::tips 这里涉及到一些历史问题
ts 出现的时间点是在 es6 发布的时间(15年)之前的,那时候 es 官方还没有推出自己的模块化标准。在 15 年之前,可以理解为是模块化标准群魔乱舞的时代,那时候 ts 也整出了自己的模块化标准,不过在 es6 module 标准出来之后。ts 立刻就支持了 es6 的模块化标准,ts 自己的那套标准,已经淹没在历史长河中了。
现阶段,我们在 ts 中书写模块化语句,只要关注 es6 module 即可。 :::
{
"compilerOptions": {
"target": "es6", // 配置我们写的 ts 的模块化规范
"module": "es6", // 配置编译结果 js 的模块化规范
},
}
codes
export const nickname = "dahuyou";
export function sum(a: number, b: number) {
return a + b;
}
import { nickname, sum } from "./myModule";
console.log(nickname); // => dahuyou
console.log(sum(1, 2)); // => 3
和我们之前写 es6 module 时,完全一样,直接写就完事。
但是,也有一些需要注意的细节问题:
- 智能提示问题
- 后缀名问题
智能提示问题
当我们输入 nick 时,它就会弹出智能提示,我们只需要按下回车,即可将其导入:
之所以会有这么有好的智能提示,是因为 TS 拥有完整的类型检查,当我们去访问一个当前模块中不存在的玩意儿的时候,ide 就会帮我们去各个模块中查找,这样对于我们开发效率的提升,是非常有帮助的。
问:为啥在记笔记测试的时候木有智能提示? 答:有可能是因为层次结构太乱了。
:::warning 如果智能提示一切正常,直接跳过以下内容。 :::
👇🏻 记录的内容是描述一个记笔记时的怪异现象
环境:该问题是当时是在 windows 系统上出现的,在 mac 上时不存在。
尝试只打开 codes 目录,如下图所示,这样就会有智能提示了。
自动从带有基本导出 nickname 的模块中导入 nickname
以上介绍的所有智能提示,都有一个前提:使用的是具名导出(也叫基本导出)。
如果使用匿名导出,那么是没有那么完善的智能提示的。
道理很简单,具名导出是有名字的,有名字才能找;匿名导出是没有名字的,没名字了咋找呢。
具名导出和匿名导出
- 具名导出:又叫基本导出,比如
export const nickname = 'dahuyou';
- 匿名导出:又叫默认导出,比如
export default 'dahuyou';
小结:
若想要享用智能提示的效果,那么我们应该使用基本导出的形式。若我们使用的是默认导出的形式,那么 ts 是不知道我们导出的东西是什么名字的,它就没法给我们提供智能提示。
后缀名问题
我们在导入文件的时候,不要加 .ts
后缀名。
Q & A
🤔 ts 中使用 es6 module 导出内容,为什么推荐使用具名导出? 为了享受 ide 为我们提供的更加友好的智能提示
🤔 为什么在导入模块的时候,不能带上 .ts 后缀名呢?
道理很简单,这些内容在编译后,是不会发生变化的,而编译后的编译结果中,都是 js 文件。
比如:import { name, sum } from “./myModule.ts”
在编译之后,它还是:import { name, sum } from “./myModule.ts”
但是编译后都是 js 文件,myModule.ts 已经变为了 muModule.js,这时候编辑结果就没法正常运行了。
PS:加上 .ts 后缀确实是报错了的,但是依旧可以完成编译。(虽然不知道原因)
5-2. 编译结果中的模块化
前言
任务列表
- 理解编译结果
-
tsc --watch
-
noImplicitUseStrict
编译结果中不包含"use strict"
-
removeComments
编译结果移除注释
难点在于:理解「使用 es6 模块化规范所写的模块,转换为等效的 commonjs 规范」的代码。
参考资料
原文链接:
cubox 个人链接:
因为在编译结果中,出现了 void 0
,由于不理解,就上网找了篇相关文章查阅。这篇文章已经读完,感觉写得非常棒,把 void
操作符的用法讲解的明明白白,忘记了的话,可以会看一下这篇文章。
原文链接:
个人链接:
编译结果中还出现了这种写法 —— (0, function)(param)
一句话总结:这种写法令函数体中的 this 指向全局对象。
如果不理解的话,可以参考一下上述文章。
版本问题:由于版本不同的原因,最新 v4.7 版本,生成的编译结果和袁进老师当时授课时使用的版本有所不同。不过大体上是都一致的,重点在于能够理解即可。
notes
target 和 module
目标文件和编译结果中的模块化标准,都是可以配置的。
{
"compilerOptions": {
"target": "xxx", // => 配置我们在 ts 文件中写的模块化标准
"module": "xxx", // => 配置编译结果的模块化标准
},
}
tsc --watch
使用该命令,用于监听 ts 文件的变化,一旦文件内容发生了变化,那么就会重新编译。(只编译,不运行)
目标文件和编译结果都是 es6
{
"compilerOptions": {
"target": "es6",
"module": "es6",
"lib": [
"es2016"
],
"outDir": "./dist",
"strictNullChecks": true
},
"include": [
"./src"
],
}
先给出结论:当 target 和 module 配置的都是 es6 module 标准时,生成的编译结果和我们写的 ts 文件,模块化部分是保持不变的。
目标文件
export const nickname = "dahuyou";
export function sum(a: number, b: number) {
return a + b;
}
import { nickname, sum } from "./myModule";
console.log(nickname); // => dahuyou
console.log(sum(1, 2)); // => 3
编译结果
export const nickname = "dahuyou";
export function sum(a, b) { // 少了类型约束信息
return a + b;
}
import { nickname, sum } from "./myModule";
console.log(nickname); // => dahuyou
console.log(sum(1, 2)); // => 3
removeComments
从上面的编译结果中可以看出,我们在 ts 中编写的相关注释,也是会生成到编译结果中的。若我们想要去掉注释,只需要加上一个配置 removeComments,将其值设置为 true 即可。
{
"compilerOptions": {
"removeComments": true
},
}
目标文件 - es6 | 编译结果 - commonjs
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"lib": [
"es2016"
],
"outDir": "./dist",
"strictNullChecks": true,
"removeComments": true
},
"include": [
"./src"
],
}
目标文件
import sayHello, { nickname, sum } from "./myModule";
console.log(nickname); // => dahuyou
console.log(sum(1, 2)); // => 3
sayHello(); // => hello world.
export const nickname = "dahuyou";
export function sum(a: number, b: number) {
return a + b;
}
export default () => {
console.log("hello world.");
};
编译结果
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const myModule_1 = require("./myModule");
console.log(myModule_1.nickname);
console.log((0, myModule_1.sum)(1, 2));
(0, myModule_1.default)();
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sum = exports.nickname = void 0;
exports.nickname = "dahuyou";
function sum(a, b) {
return a + b;
}
exports.sum = sum;
exports.default = () => {
console.log("hello world.");
};
**"use strict";**
表示开启严格模式
:::tips
其实没必要开启严格模式,我们写的 ts 代码比严格模式的约束还要强很多,所以编译结果中的这个严格模式是可以直接去掉的。
通过 noImplicitUseStrict
配置可选择是否在编译结果中包含 "use strict";
这条语句。
:::
**Object.defineProperty(exports, "__esModule", { value: true });**
等效:exports.__esModule = true
:::tips
该语句的作用,暂时不用管,之后会介绍。
:::
**exports.sum = exports.nickname = void 0;**
等效:exports.sum = undefined; exports.nickname = undefined;
这种做法就相当于我们在模块中声明了两个变量 sum
、nickname
。但是还没有给变量进行赋值。
:::tips
void 操作符还有其它用法,详情可以查看参考资料。
:::
**(0, myModule_1.sum)(1, 2)**
、**(0, myModule_1.default)();**
这么写的目的,是为了绑定方法中的 this,令 this 指向全局而非 myModule。
不要误以为上述写法等效于:
myModule_1.sum(1, 2)
、myModule_1.default()
因为这两种方式调用函数,函数中的 this 指向是不一样的。
myModule_1.sum(1, 2)
这种方式,函数中的 this 将指向 myModule 对象(0, myModule_1.sum)(1, 2)
这种方式,函数中的 this 将指向全局对象而我们在编译之前所写的 sum 方法,它里边如果用到了 this,那么 this 就是指向全局的。第二种写法的作用就是为了确保方法中的 this 指向依旧指向全局。
**exports.default = () => { console.log("hello world."); }**
es6 的默认导出,转为 commonjs 后,会转为 exports 的 default 属性。
**const myModule_1 = require("./myModule");**
简单说明一下这个 1,它表示当前模块 ./myModule
是第一次被使用。如果我们将导入语句改为下面这种写法:
import sayHello from "./myModule";
import { nickname, sum } from "./myModule";
console.log(nickname); // => dahuyou
console.log(sum(1, 2)); // => 3
sayHello(); // => hello wo
Object.defineProperty(exports, "__esModule", { value: true });
const myModule_1 = require("./myModule");
const myModule_2 = require("./myModule");
console.log(myModule_2.nickname);
console.log((0, myModule_2.sum)(1, 2));
(0, myModule_1.default)();
会发现第二次使用 ./myModule
模块时,后边的编号会自动自增。
noImplicitUseStrict
若想去掉编译结果中生成的 "use strict"
,那么需要配置 noImplicitUseStrict
。
{
"compilerOptions": {
"noImplicitUseStrict": true // 去掉严格模式
},
}
Q & A
🤔 编译结果中的模块化标准,是否是可配置的? 是
可以通过 tsconfig.json 中的 module
字段来配置;
编译结果中的模块化标准,可以配置为 es6 module、commonjs 等等,但是最常用的就是这两个,并且我们也只需要掌握好这两个即可;
🤔 tsc —watch 这个命令有什么用? 当我们编写的 ts 文件发生变化时,它会帮我们自动进行编译,生成对应的 js 文件;
并不会自动执行编译结果文件
🤔 默认情况下,编译结果是否是严格模式? 是
配置 compilerOptions.noImplicitUseStrict: true
去掉编译结果中的严格模式
🤔 默认情况下,ts 中的注释,是否会生成到编译结果中? 会
配置 compilerOptions.removeComments: true
移除编译结果中的注释
🤔 默认情况下,编译目标 ts 文件发生错误,是否可以正常编译? 可以
通过配置 compilerOptions.noEmitOnError
,可以设置编译目标发生错误时,无法生成编译结果。
5-3. 解决默认导入的错误
前言
使用 es6 module,还有一些细节上的问题待处理,比如我们想要访问 node 中的一个内置模块 fs
,使用这种 import 的方式来导入,是不可行的。该问题如何解决,就是本节课所要介绍的重点。
任务列表
-
compilerOptions.noEmitOnError
-
compilerOptions.esModuleInterop
参考资料
notes
错误原因分析
在目标文件中,尝试导入 node 环境下的 fs 模块。
import fs from "fs";
fs.readFileSync("./");
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = require("fs");
fs_1.default.readFileSync("./");
fs_1.default.readFileSync("./")
通过编译结果,我们可以非常清楚地看出,为何这种写法会出现问题。
我们在 ts 文件中写的 fs.xxx
代码,被编译后会转换为 fs_1.default.xxx
。这就是为什么我们不能使用上面这种写法来导入 fs 的原因。
正确的编译结果应该是 fs_1.readFileSync
出现这种现象的本质原因在于:node 的内置 fs 模块,它并非使用 es6 module 规范来写的,它压根就没有 export default 这一说。
cmd + f
全局搜一下 module.exports
,会发现 fs 模块实际上采用的是 commonjs 规范来导出的。
ts 中为了处理 commonjs 和 es6 两者之间的交互,对于默认导出采用 default 属性来表示,这是我们上一节课中介绍过的内容。所以对于我们这种传统的 es6 module 导入默认导出内容的写法,默认是会报错的。
:::warning 充分理解发生错误的原因,对于后续内容的理解非常重要。 :::
细节:导入的模块未使用
若我们在 ts 中导入了一个模块,但是没有使用该模块,那么该模块的导入语句并不会生成到编译结果中。比如上述代码,若我们只是导入了 fs 模块,但是没有使用该模块,那么导入语句就不会生成到编译结果中。
方案1:使用具名导入
import { readFileSync } from "fs";
readFileSync("./");
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = require("fs");
(0, fs_1.readFileSync)("./");
导入具名导出**import { readFileSync } from "fs"**
虽然使用默认导出不行,但是我们可以使用基本导出(具名导出),这样导入的内容,编译结果就是正常的。
新的问题
fs 中的具名导出那么多,我们不知道具体有哪些具名导出;而且每要使用一个 fs 模块提供的 api,就到在 import 中写上该 api,操作上显得有点繁琐。
方案2:import * as fs from "fs"
若我们希望将 fs 模块导出的所有内容,丢到一个变量中,然后我们通过 变量.xxx
的形式来访问指定的 api,使用上面这种写法就可以了。
import * as fs from "fs";
fs.readFileSync("./");
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
fs.readFileSync("./");
这种方案也是 ts 官方建议我们这么做的
会发现,只要我们将鼠标的悬停在 fs 上,vscode ide 就会展示出该模块的一些描述信息,在描述信息中,我们也可以看到正确的导入方式应该如何书写。
也可以按下 cmd 键,加鼠标左键,快速定位到 fs 模块的类型声明文件中查看相关描述信息。
方案3:开启 esModuleInterop
文档:链接
默认值:false
含义:是否开启 es module 和 commonjs 模块化标准之间的交互。
import fs from "fs"; // => ×
import { readFileSync } from "fs"; // => √
import * as fs from "fs"; // => √
虽然前面介绍的两种解决方式,都可以解决我们提到的错误式写法所导致的问题。但是,我们在写代码的时候,还是很有可能会写出错误写法那样的代码。对于该问题,我们加上 esModuleInterop
配置来解决。
下面介绍的内容,就是给强迫症患者,非得使用 import fs from "fs";
这种写法来写的。
{
"compilerOptions": {
"esModuleInterop": true,
},
}
在加上该配置过后,我们再来看看原来错误写法的代码:
import fs from "fs";
fs.readFileSync("./");
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
fs_1.default.readFileSync("./");
👇🏻 简单来分析一下编译后的结果,看看 ts 是如何帮我们处理的。
首先先看看结论:__importDefault
是一个辅助函数,该函数的返回值要么是 require("fs")
要么是 { default: require("fs") }
,由于现在是不会报错的,所以返回的必然是后者。其实就是帮我们包裹了一层,将 __importDefault(require("fs"))
转为了 { "default": require("fs") }
也就是说,下面这两条语句其实是等效的:
const fs_1 = __importDefault(require("fs"))
const fs_1 = { "default": require("fs") }
当我们再去访问:fs_1.default.readFileSync("./")
这个时,实际上就是在访问 require("fs").readFileSync("./")
其实为什么是后者 { "default": mod }
也不难分析:
(this && this.__importDefault)
- 先看全局对象是否存在,如果存在,那么在看全局对象上边是否已经有了辅助函数
__importDefault
如果已经存在了__importDefault
那么就没必要再将后续的函数表达式赋值给__importDefault
了 - 如果当前模块中还没有创建辅助函数
__importDefault
那么就初始化一个辅助函数
- 先看全局对象是否存在,如果存在,那么在看全局对象上边是否已经有了辅助函数
(mod && mod.__esModule) ? mod : { "default": mod }
- mod 参数,表示的就是我们导入的模块
- 先确保我们导入的模块是存在的,如果不存在,那么直接将
{ "default": mod }
给返回 - 再确保当前模块是否是 es 模块,如果不是,那么直接将
{ "default": mod }
给返回 - 如果当前导入的模块是存在的,并且是使用 es 规范来写的,那么直接将
mod
给返回
显然,我们这里所要导入的 fs 模块,它是使用 commonjs 规范来写的,并非一个 es 模块,所以最终返回的会是 { "default": mod }
noEmitOnError
错误时不生成编译结果。
let str: string = 123; // => ×
虽然上述代码在 ts 中是会报错的。但是,类型约束并不会生成到 js 中,所以在 js 中,是不会报错的。默认情况下,依旧可以对该 ts 文件进行编译,编译生成的 js 文件的内容如下:
let str = 123;
若我们想让 ts 在报错的情况下,无法进行编译,那么需要配置 onEmitOnError 字段:
{
"compilerOptions": {
"noEmitOnError": true,
},
}
5-4. 如何在 TS 中书写 commonjs 模块化代码
前言
任务列表
- 掌握如何在 ts 中书写 commonjs 模块化代码
- 导出:
export = xxx
- 导入:
import xxx = require("xxx")
- 导出:
notes
在 ts 中编写模块化代码,最好就直接使用 es module 规范来写,前面已经介绍过相关细节。
若非要使用 commonjs 规范来写,也不是不行,本节将介绍如何在 ts 中书写 commonjs 规范的模块化代码。
通过本节介绍的内容,我们将会认识到:为什么推荐在 ts 中推荐直接使用 es module 规范来写模块化代码;在 ts 中书写 commonjs 模块化代码都有哪些问题。
无法获取智能提示
因为 ts 是 js 的超级,所以我们完全按照传统 commonjs 的书写规范来写,是 OK 的。
module.exports = {
name: "dahuyou",
sum(a: number, b: number) {
return a + b;
}
}
const myModule = require("./myModule");
编译结果
module.exports = {
name: "dahuyou",
sum(a, b) {
return a + b;
}
}
const myModule = require("./myModule");
虽然这种写法,程序也能正常运行,但是它有一个缺点:我们将无法获取到类型检查。
我们导入的内容会被识别为 any 类型,自然也就没法获取到智能提示。
获取智能提示
若想获取到类型检查,我们不应该采用这种方式来写。下面介绍若想要获取类型提示,应该如何书写。
export = {
name: "dahuyou",
sum(a: number, b: number) {
return a + b;
}
}
import myModule from "./myModule"; // 写法1
// import myModule = require("./myModule"); // 写法2
导出:将原来的 module.exports = xxx
改为 export = xxx
导入:将原来的 const myModule = require("./myModule")
改为 import myModule from "./myModule"
或者 import myModule = require("./myModule")
:::warning
这么写的前提:配置 compilerOptions.esModuleInterop
为 true
:::
编译结果
module.exports = {
name: "dahuyou",
sum(a, b) {
return a + b;
}
}
Object.defineProperty(exports, "__esModule", { value: true });
编译结果几乎是完全一致的,虽然 1.js 的编译结果有所不同,但是和之前的编译结果相比,功能都是一样的。
对比上面那种安全按照 commonjs 规范来写,这种写法的优势在于可以得到类型检查,提供更加友好的智能提示。
小结
导出的写法:
导入的写法:
Q & A
🤔 为什么推荐我们在 ts 中使用 es module 来写? 在 ts 中编写模块化代码,推荐直接使用 es module 规范来写,下面是几点原因:
- es module 是官方推出的,commonjs 更有可能会推出历史舞台
es module
是官方推出的模块化规范commonjs
是社区规范
除了 es module
、commonjs
之外,还有很多其他的模块化规范。
但是随着时间推移,规范应该会逐步统一,大多规范都将成为历史,直接使用官方推出的 es module
规范来写模块化语句即可。
- 在 ts 中书写 commonjs 模块化代码很别扭
如果完全按照 commonjs 规范来写,那么 ts 没法给我们提供类型检查和智能提示;
如果想要智能提示,那么我们写的模块化代码感觉是 es module 和 commonjs 的杂交品;
5-5. 模块解析
前言
任务列表
-
compilerOptions.moduleResolution
配置 ts 中采用的模块解析策略
参考资料
查阅 compilerOptions.moduleResolution
该配置的相关描述
notes
{
"compilerOptions": {
"moduleResolution": "node",
// 配置模块解析策略为 node
// 其实默认值就是 node
}
}
模块解析是什么
模块解析:应该从什么位置查找模块
在 TS 中,有两种模块解析策略:
- classic:经典模块解析策略
- node:node 解析策略
classic 解析策略
classic 这种解析策略,在 es6(2015年)出现之前就已经有了,它是 TS 自身特有的模块解析策略。
但是,classic 经典模块解析策略在目前已经过时了,如果我们有幸接触到一些七、八年前用 ts 写的项目,那才有可能与之相遇。
所以呢,它的规则,这里就不赘述了。
node 解析策略
node 解析策略和 node 环境下的模块化细节几乎完全相同,唯一的变化就是将 js 替换为 ts,以下是对 node 解析策略的简单说明:
- 相对路径:
require("./xxx")
- 先看当前目录下的
xxx
文件; - 再将其视作一个目录,尝试去找目录下的
package.json
文件下的main
字段; - 不断往上找;
- 先看当前目录下的
- 非相对路径:
require("xxx")
- 先看当前目录下的
node_modules
目录; - 不断往上找;
- 先看当前目录下的
:::tips
详情: 1-3. Node的模块化细节
之后该链接可能会失效,会将其合并到 1. Node 核心 中,如果失效了,请前往知识库 node 查看
PS:对于如何查找模块,这个基本上不需要担心,很少会出现引入错误模块的情况。所以查找模块的细节规则,这个就非常矛盾,袁进老师在 node 的教程中,把这一块介绍地非常详细,但是引入错误模块的可能性实在太小。诶,当时也记录地非常详细,怎么说呢,这一块。有精力就看看吧,没精力就跳过好了,这一块的细节太多,现在也基本忘记了。 :::
5-6. 练习:使用模块化优化扑克牌程序
前言
任务列表
- 基于4. 扩展类型-枚举(4-3 的练习)独立完成本节练习
- 重置编译结果目录
rm -rf dist && tsc
- macrd /s /q xxx & tsc
- windows
- 理解类型声明模块(types.ts),在编译时将被忽略
参考资料
notes
在 windows 和 mac 下,该如何通过命令行的方式来删除一个目录,并在删除目录之后,立即执行其它命令?
**rm -rf dist && tsc**
若是 mac 用户,使用该命令重置编译结果目录 dist
**rd /s /q xxx & tsc**
若是 windows 用户,使用该命令重置编译结果目录 dist
:::tips
rd
,表示 remove directory 删除目录
:::
codes
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"lib": [
"es2016"
],
"outDir": "./dist",
"strictNullChecks": true
},
"include": [
"./src"
],
}
import {createDeck, printDeck} from "./funcs"
const deck = createDeck();
printDeck(deck);
// 黑桃(Spade)、红桃(Heart)、方块(Diamond)、梅花(Club)
export enum Colors { // 花色
Heart = "❤",
Spade = "♠",
Club = "♣",
Diamond = "◇",
}
// one,two,three,four,five,six,seven,eight,nine,ten,eleven,twelve,thirteen
// 牌编号
export enum Nums {
one = "A",
two = "2",
three = "3",
four = "4",
five = "5",
six = "6",
seven = "7",
eight = "8",
nine = "9",
ten = "10",
eleven = "J",
twelve = "Q",
thirteen = "K",
}
import { Colors, Nums } from "./enums";
export type Poker = {
// 一张牌
number: Nums | "JOKER" | "joker";
color?: Colors;
};
export type Deck = Poker[]; // 一副牌
import { Colors, Nums } from "./enums";
import { Deck } from "./types";
// 创建一副牌
export function createDeck() {
const deck: Deck = [];
// 插入大小王
deck.push({
number: "joker",
});
// 插入大王
deck.push({
number: "JOKER",
});
// 插入 A ~ K
const colorKeys = Object.keys(Colors);
const numKeys = Object.keys(Nums);
for (let i = 0; i < colorKeys.length; i++) {
for (let j = 0; j < numKeys.length; j++) {
deck.push({
number: Nums[numKeys[j]],
color: Colors[colorKeys[i]]
});
}
}
deck.sort(() => Math.random() - 0.5); // 打乱牌序
return deck;
}
// 打印一副扑克
export function printDeck(deck: Deck) {
let result = "底牌:";
const desk = deck.splice(0, 3); // 底牌
desk.forEach((poker, i) => {
result += poker.color ? ` ${poker.color}${poker.number}` : ` ${poker.number}`;
});
result += '\n用户1:'
deck.forEach((poker, i) => {
if(i % 17 === 0 && i !== 0) result += `\n用户${i / 17 + 1}:`;
result += poker.color ? ` ${poker.color}${poker.number}` : ` ${poker.number}`;
});
console.log(result);
}
{
"devDependencies": {
"@types/node": "^17.0.22"
},
"scripts": {
"dev": "nodemon --watch src -e ts --exec ts-node ./src/index.ts",
"build": "rm -rf dist && tsc"
}
}
场景描述:
当我们执行 tsc 命令时,dist 目录中已存在一些 .js 文件,比如 1.js
若此时直接执行 tsc 命令对我们所写的目标文件 .ts 文件进行编译,那么 1.ts
默认会依旧存在,并不会被覆盖
如果我们想要每次执行 tsc 命令之前,都先将存放编译结果文件的目录 dist 目录给提前清空,那么该如何实现呢?
- 如果是 windows 用户:
rd /s /q dist & tsc
- 如果是 mac 用户:
rm -rf dist && tsc
执行 npm run dev
:在内存中进行编译并运行
编译结果:对于编译结果,这里做一点说明,那就是对类型别名的处理。
import { Colors, Nums } from "./enums";
export type Poker = {
// 一张牌
number: Nums | "JOKER" | "joker";
color?: Colors;
};
export type Deck = Poker[]; // 一副牌
由 types.ts 中的内容不难看出,这里面导出的内容都是类型别名
前面介绍过,类型别名并不会生成到编译结果中,所以对于 types.ts 模块,ts 会进行特殊处理
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
相当于编译结果仅仅是生成了一个空模块。
若原来的编译目标文件中,有文件引用了该模块,那么在编译结果中,引用代码会被直接忽略。
import { Colors, Nums } from "./enums";
import { Deck } from "./types";
// 创建一副牌
export function createDeck() {
const deck: Deck = [];
// 插入大小王
deck.push({
number: "joker",
});
// 插入大王
deck.push({
number: "JOKER",
});
// 插入 A ~ K
const colorKeys = Object.keys(Colors);
const numKeys = Object.keys(Nums);
for (let i = 0; i < colorKeys.length; i++) {
for (let j = 0; j < numKeys.length; j++) {
deck.push({
number: Nums[numKeys[j]],
color: Colors[colorKeys[i]]
});
}
}
deck.sort(() => Math.random() - 0.5); // 打乱牌序
return deck;
}
// 打印一副扑克
export function printDeck(deck: Deck) {
let result = "底牌:";
const desk = deck.splice(0, 3); // 底牌
desk.forEach((poker, i) => {
result += poker.color ? ` ${poker.color}${poker.number}` : ` ${poker.number}`;
});
result += '\n用户1:'
deck.forEach((poker, i) => {
if(i % 17 === 0 && i !== 0) result += `\n用户${i / 17 + 1}:`;
result += poker.color ? ` ${poker.color}${poker.number}` : ` ${poker.number}`;
});
console.log(result);
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.printDeck = exports.createDeck = void 0;
const enums_1 = require("./enums");
// 创建一副牌
function createDeck() {
const deck = [];
// 插入大小王
deck.push({
number: "joker",
});
// 插入大王
deck.push({
number: "JOKER",
});
// 插入 A ~ K
const colorKeys = Object.keys(enums_1.Colors);
const numKeys = Object.keys(enums_1.Nums);
for (let i = 0; i < colorKeys.length; i++) {
for (let j = 0; j < numKeys.length; j++) {
deck.push({
number: enums_1.Nums[numKeys[j]],
color: enums_1.Colors[colorKeys[i]]
});
}
}
deck.sort(() => Math.random() - 0.5); // 打乱牌序
return deck;
}
exports.createDeck = createDeck;
// 打印一副扑克
function printDeck(deck) {
let result = "底牌:";
const desk = deck.splice(0, 3); // 底牌
desk.forEach((poker, i) => {
result += poker.color ? ` ${poker.color}${poker.number}` : ` ${poker.number}`;
});
result += '\n用户1:';
deck.forEach((poker, i) => {
if (i % 17 === 0 && i !== 0)
result += `\n用户${i / 17 + 1}:`;
result += poker.color ? ` ${poker.color}${poker.number}` : ` ${poker.number}`;
});
console.log(result);
}
exports.printDeck = printDeck;
编译结果中仅对 import { Colors, Nums } from "./enums";
进行了编译;忽视了对 import { Deck } from "./types";
的编译。