开发流程中的 TypeScript

128 阅读8分钟

开发流程中的 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 配置。