开发流程中的 TypeScript
探索 TypeScript 与 JavaScript 的集成、CLI 设置以及如何为 Web 应用程序进行无缝转译。立即改善您的开发工作流程。
我们已经探讨了 JavaScript 和 TypeScript 之间的关系,以及 TypeScript 如何改善您作为开发者的生活。但让我们更深入一些。在本章中,我们将启动并运行 TypeScript CLI,并了解它如何融入开发流程。
举个例子,我们将研究如何使用 TypeScript 构建一个 Web 应用程序。但 TypeScript 也可以用在任何可以使用 JavaScript 的地方——例如 Node、Electron、React Native 或任何其他应用程序中。
TypeScript 在浏览器中遇到的问题
考虑这样一个 TypeScript 文件 example.ts,它包含一个 run 函数,该函数会向控制台打印一条消息:
const run = (message: string) => {
console.log(message);
};
run("Hello!");
与 example.ts 文件一起,我们有一个基本的 index.html 文件,它在一个 script 标签中引用了 example.ts 文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My App</title>
</head>
<body>
<script src="example.ts"></script>
</body>
</html>
然而,当我们尝试在浏览器中打开 index.html 文件时,你会在控制台中看到一个错误:
Unexpected token ':'
TypeScript 文件中并没有红色波浪线,那么为什么会发生这种情况呢?
运行时无法直接运行 TypeScript
问题在于浏览器(以及像 Node.js 这样的其他运行时环境)本身无法理解 TypeScript。它们只理解 JavaScript。
在 run 函数的例子中,函数声明中 message 后面的 : string 并非有效的 JavaScript 语法:
const run = (message: string) => {
// \`: string\` 不是有效的 JavaScript!
console.log(message);
};
去掉 : string 后,代码看起来更像是 JavaScript 了,但现在 TypeScript 会在 message 下方显示一个错误:
const run = (message) => {};
// Parameter 'message' implicitly has an 'any' type.7006
在 VS Code 中,将鼠标悬停在红色波浪线上,我们可以看到 TypeScript 的错误消息告诉我们 message 隐式地具有 any 类型。
我们稍后会讨论这个特定错误的含义,但现在的重点是,我们的 example.ts 文件包含了浏览器无法理解的语法,而如果我们移除它,TypeScript CLI 又会不满意。
因此,为了让浏览器理解我们的 TypeScript 代码,我们需要将其转换为 JavaScript。
转译 TypeScript
将 TypeScript 转换为 JavaScript 的过程(称为“转译”)可以由 TypeScript CLI tsc 来处理,它在您安装 TypeScript 时一同安装。但在我们使用 tsc 之前,需要先设置我们的 TypeScript 项目。
打开一个终端,并导航到 example.ts 和 index.html 的父目录。
要仔细检查您是否正确安装了 TypeScript,请在终端中运行 tsc --version。如果您看到版本号,那么一切就绪。否则,请使用 PNPM 全局安装 TypeScript:
pnpm add -g typescript
在终端打开到正确的目录并且 TypeScript 已安装的情况下,我们可以初始化我们的 TypeScript 项目了。
初始化 TypeScript 项目
为了让 TypeScript 知道如何转译我们的代码,我们需要在项目的根目录下创建一个 tsconfig.json 文件。
运行以下命令将生成 tsconfig.json 文件:
tsc --init
在新建的 tsconfig.json 文件内部,您会找到许多有用的初始配置选项以及许多其他被注释掉的选项。
目前,我们只使用默认配置:
// tsconfig.json 文件片段
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
有了 tsconfig.json 文件之后,我们就可以开始转译了。
运行 tsc
在终端中不带任何参数运行 tsc 命令,将会利用 tsconfig.json 中的默认设置,并将项目中的所有 TypeScript 文件转译为 JavaScript。
tsc
在这种情况下,这意味着我们 example.ts 文件中的 TypeScript 代码将变成 example.js 文件中的 JavaScript 代码。
在 example.js 内部,我们可以看到 TypeScript 语法已经被转译成了 JavaScript:
// example.js 文件内部
"use strict";
const run = (message) => {
console.log(message);
};
run("Hello!");
现在我们有了 JavaScript 文件,可以更新 index.html 文件,使其引用 example.js 而不是 example.ts:
// index.html 文件内部
<script src="example.js"></script>
现在在浏览器中打开 index.html 文件,控制台将显示预期的 "Hello!" 输出,并且没有任何错误!
TypeScript 会改变我的 JavaScript 吗?
看看我们的 JavaScript 文件,我们可以发现它与 TypeScript 代码相比几乎没什么变化。
"use strict";
const run = (message) => {
console.log(message);
};
run("Hello!");
它移除了 run 函数中的 : string,并在文件顶部添加了 "use strict";。但除此之外,代码是完全相同的。
这是 TypeScript 的一个关键指导原则——它在 JavaScript 之上为您提供了一个薄薄的语法层,但它不会改变您代码的运行方式。它不添加运行时验证,也不尝试优化代码的性能。
它只是添加类型(以提供更好的开发者体验 DX),然后在将代码转换为 JavaScript 时移除它们。
这个指导原则有一些例外,例如枚举(enums)、命名空间(namespaces)和类参数属性(class parameter properties)——但我们稍后会介绍这些。
关于版本控制的说明
我们已经成功地将 TypeScript 代码转译为 JavaScript,但同时也向项目中添加了一个新文件。在项目根目录添加一个 .gitignore 文件并包含 *.js,可以防止 JavaScript 文件被添加到版本控制中。
这很重要,因为它向使用该仓库的其他开发者传达了 *.ts 文件是 JavaScript 的真正源头。
以观察模式运行 TypeScript
您可能已经注意到了。如果您对 TypeScript 文件进行了一些更改,您需要再次运行 tsc 才能在浏览器中看到这些更改。
这样做很快就会变得繁琐。您可能会忘记执行,然后奇怪为什么您的更改还没有在浏览器中生效。幸运的是,TypeScript CLI 有一个 --watch 标志,它会在保存时自动重新编译您的 TypeScript 文件:
tsc --watch
要查看实际效果,请在 VS Code 中并排打开 example.ts 和 example.js 文件。
如果您将 example.ts 中传递给 run 函数的 message 更改为其他内容,您会看到 example.js 文件自动更新。
TypeScript CLI 中的错误
如果 tsc 遇到错误,它会在终端中显示错误,并且包含错误的文件在 VS Code 中会用红色波浪线标记。
例如,尝试将 example.ts 文件中 run 函数内的 message: string 更改为 message: number:
const run = (message: number) => {
console.log(message);
};
run("Hello world!");
// Argument of type 'string' is not assignable to parameter of type 'number'.2345
在终端内部,tsc 将显示一条错误消息:
// 终端内部
Argument of type 'string' is not assignable to parameter of type 'number'.
run("Hello world!");
Found 1 error. Watching for file changes.
将更改改回 message: string 将会消除错误,并且 example.js 文件将再次自动更新。
在观察模式下运行 tsc 对于自动编译 TypeScript 文件并在编写代码时捕获错误非常有用。
它在大型项目中尤其有用,因为它会检查整个项目。这与您的 IDE 不同,IDE 通常只显示当前打开文件的错误。
TypeScript 与现代框架
到目前为止,我们的设置非常简单:一个 TypeScript 文件,一个 tsc –watch 命令,以及一个 JavaScript 文件。但是要构建一个前端应用程序,我们需要做的事情远不止这些。我们需要处理 CSS、代码压缩(minification)、打包(bundling)等等。TypeScript 无法帮助我们完成所有这些工作。
幸运的是,有许多前端框架可以提供帮助。
Vite 就是一个前端工具套件的例子,它不仅能将 .ts 文件转译为 .js 文件,还提供了一个具有模块热替换(Hot Module Replacement, HMR)功能的开发服务器。使用 HMR 配置,您可以在浏览器中看到代码更改的实时反映,而无需手动刷新页面。
但这有一个缺点。虽然 Vite 和其他工具处理 TypeScript 到 JavaScript 的实际转译,但它们默认不提供类型检查。这意味着您可能会在代码中引入错误,而 Vite 会继续运行开发服务器而不会通知您。它甚至可能允许您将错误推送到生产环境,因为它对此一无所知。
所以,我们仍然需要 TypeScript CLI 来捕获错误。但是如果 Vite 正在转译我们的代码,我们就不需要 TypeScript 也来做这件事了。
TypeScript 作为 Linter (代码检查工具)
幸运的是,我们可以配置 TypeScript 的 CLI 以进行类型检查,而不会干扰我们的其他工具。
在 tsconfig.json 文件中,有一个名为 noEmit 的选项,它告诉 tsc 是否生成 JavaScript 文件。
通过将 noEmit 设置为 true,当您运行 tsc 时将不会创建 JavaScript 文件。这使得 TypeScript 的行为更像一个 Linter(代码检查工具)而不是一个转译器。这使得 tsc 步骤成为 CI/CD 系统的一个极好补充,因为它可以防止合并带有 TypeScript 错误的拉取请求(pull request)。
在本书的后续部分,我们将更深入地研究用于应用程序开发的更高级的 TypeScript 配置。