开发流程中的 TypeScript

7 阅读1分钟

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