迁移到 TypeScript

2025-06-07

迁移到 TypeScript

在本文中,我们将讨论TypeScript、它的优点以及如何将其引入到遗留的 JavaScript 代码中。

阅读完本文后,您将了解到:

  • TypeScript 是什么以及它的优点和缺点是什么
  • 如何在旧版 JavaScript 代码库中开始使用 TypeScript
  • 如何在 TypeScript 中使用类型注解
  • 如何在 TypeScript 中使用可空性检查
  • 进一步改进 TypeScript 代码的后续步骤

什么是 TypeScript?

那么,什么是 TypeScript 以及为什么要使用它?

简而言之,TypeScript 是 JavaScript 的超集。可以将其视为带有附加注解和静态类型检查的 JavaScript。

TypeScript可以转译为 JavaScript,因此任何支持 JavaScript 的浏览器都可以运行用 TypeScript 编写的代码。TypeScript 还可以支持旧版本的 JavaScript。这让您可以使用现代 JavaScript 功能,例如类、箭头函数let/const和模板字符串,即使这些功能尚未支持浏览器。

此外,TypeScript 的静态检查使得整个类别的缺陷不可能发生,这是我非常强烈感受到的

通过这个简短的介绍,让我们来了解一下我们将要迁移到 TypeScript 的应用程序。

示例应用程序

我们将使用一个简单的 JavaScript 应用程序,并将其迁移到 TypeScript。

该代码可在 GitHub 上获取,包括其初始 JavaScript 版本(存在一些 bug)和最终的 TypeScript 版本。如果您想在浏览器中使用最终修复版本,可以在线获取

该应用是一个简单的测试用例管理器,用户可以输入测试用例的名称并将其添加到列表中。然后,可以将测试用例标记为通过、失败或删除。

示例应用程序,显示测试用例列表

这是一个故意设计得简单却又有 bug 的应用。它没有使用任何 JavaScript 框架,甚至没有使用任何 JavaScript 库——甚至连 JQuery、Underscore 或 Lodash 都没有。

该应用程序确实使用了Bootstrap v4Bootswatch 的 Darkly 主题,以便让本文的 HTML 保持简单且 UI 整洁。

现有的 HTML

虽然我们的重点是 JavaScript,但 HTML 中仍有一些事项需要注意:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Migrating to TypeScript Example</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="Logic.js"></script>
<link rel="stylesheet" href="bootstrap.min.css">
</head>
<body>
<div class="container">
<a href="https://www.KillAllDefects.com" target="_blank" title="Visit Kill All Defects Website">
<img src="KillAllDefects.png" width="312" height="91" alt="Kill All Defects Logo" />
</a>
<div class="jumbotron">
<h1>Test Manager</h1>
<div>Add a test case below, then click to indicate if it is passing or failing.</div>
</div>
<div class="input-group mb-3">
<input type="text" class="form-control" id="txtTestName" placeholder="Enter test case name" aria-label="Test Case Name" aria-describedby="button-add">
<div class="input-group-append">
<button class="btn btn-primary" type="button" id="button-add" onclick="addTestCase()">Add</button>
</div>
</div>
<div>
<h2>Test Cases</h2>
<div id="lblNoTestCases" class="text-muted">There are no test cases. Click add test case above to get started.</div>
<div id="listTestCases" class="list-group"></div>
</div>
</div>
</body>
</html>
view raw JSApp.html hosted with ❤ by GitHub

具体来说,我们来看几行:

  • 第 7 行导入我们的主要 JavaScript 代码
  • 第 22 行引用了addTestCase我们 JavaScript 代码中定义的内容。
  • 第 27 行 –lblNoTestCases是当不存在测试用例时显示的标签
  • 第 28 行 –listTestCases是测试用例 UI 元素的占位符

启动 JavaScript 代码

除此之外,让我们分几部分来看现有的代码:

这里我们定义了一个TestCase类,作为此应用程序中的主要(也是唯一的)实体。我们testCases在第 1 行定义了一个 集合,用于保存当前状态。在第 20 行,我们添加了一个启动事件处理程序,用于生成初始应用程序数据并调用函数来更新测试用例。

非常简单,尽管它确实包含至少一个错误(看看在我稍后指出之前你是否能找到它)。

渲染 JavaScript 代码

现在,让我们看看列表渲染的代码。由于我们没有使用模板引擎或像 Angular、Vue 或 React 这样的花哨的单页应用框架,所以代码不太美观。

这段代码解释起来相对清晰,首先清空列表项,然后将每个项添加到列表中。我从未说过它很高效,但作为一个演示,它确实有效。

与上一个一样,这个块包含至少一个错误。

事件处理 JavaScript 代码

最后一段代码处理来自用户的事件。

这专门处理按钮点击和将项目添加到列表。

并且,再次强调,这个块中至少有一个错误。

代码有什么问题?

那么,这里出了什么问题?嗯,我发现了以下问题:

  • 初始测试数据不可能失败或被删除。
  • 任何附加测试都不可能失败
  • 如果您可以删除所有项目,则添加项目标签将不会显示

重点不在于 bug 在哪里。重点是:这些 bug 中的每一个都能被 TypeScript 捕获。

好了,介绍完毕,让我们开始将其转换为 TypeScript。在这个过程中,我们将被迫修复每一个缺陷,最终获得不会再次出现同样问题的代码。

安装 TypeScript

如果您尚未安装 TypeScript,则需要先安装Node 包管理器 (NPM)。我建议您安装长期支持 (LTS) 版本,但您的需求可能有所不同。

安装 NPM 后,转到命令行并执行以下命令:npm i -g typescript

这将在您的计算机上全局安装 TypeScript,并允许您使用TypeScript编译所见,虽然将 TypeScript 代码转换为 JavaScript 的术语是转译” ,但人们倾向于使用“编译”和“编译”。请注意,您可能会以任何一种方式看到它——包括在本文中。tsc

完成以上步骤后,您就拥有了使用 TypeScript 所需的一切。您不需要特定的编辑器来使用 TypeScript,所以您可以使用任何您喜欢的编辑器。我更喜欢使用WebStorm来处理 TypeScript 代码,但VS Code也是一个非常流行(且免费)的替代方案。

接下来,我们将在项目中使用 TypeScript。

将我们的项目编译为 TypeScript 项目

初始化 TypeScript

打开命令行并导航到项目目录,然后运行以下命令:

tsc --init

您应该会收到一条消息,表明该内容tsconfig.json已创建。

如果需要,你可以打开这个文件看看。这个文件的大部分内容都被注释掉了,但我其实很喜欢这一点。TypeScript 提供了一个很好的配置文件,它列出了所有可以添加或自定义的内容。

现在,如果您导航到项目目录并运行,tsc您应该看到 TypeScript 显示了许多与您的文件相关的错误:

这些问题都是合理的担忧,但目前,让我们通过编辑 tsconfig.json 文件并设置来禁用一些问题"strict": false,

现在,如果你尝试编译,你会发现错误少了很多。大多数错误似乎都与这个TestCase类有关,所以现在我们来看一下。

类型注解

大多数错误似乎都存在isPassing,只是id没有在该类上定义。这很合理,因为我们使用了 JavaScript 的动态定义属性的固有能力。由于我们使用了 TypeScript 的检查功能,所以我们现在需要定义这些字段:

第 8-10 行是新增的,定义了缺失的字段。请注意,我们在: string: boolean和 的: number定义中使用了类型注解语法。

类型断言

接下来,我们将解决该addTestCase方法中的一个问题。这里,TypeScript 抱怨HTMLElement没有value字段。没错,但我们实际提取的元素是一个文本框,它显示为HTMLInputElement。因此,我们可以添加一个类型断言,告诉编译器该元素是更具体的类型。

修改后的代码如下:

const textBox = <HTMLInputElement>document.getElementById('txtTestName');

重要提示: TypeScript 的检查是在编译时进行的,而不是在实际的运行时代码中。这里的概念是在编译时识别错误,并保持运行时代码不变。

纠正错误代码

TSC也抱怨我们的一些for循环,因为我们稍微作弊了,省略了var这些循环的语法。TypeScript 不会再让我们作弊了,所以让我们在updateTestCases和中修复这些问题,在声明前面findTestCaseById添加一个语句,如下所示:const

function findTestCaseById(id) {
    for (const testcase of this.testCases) {
        if (testcase.id === id) return testcase;
    }

    return null;
}
Enter fullscreen mode Exit fullscreen mode

修复错误

现在,据我统计,还有两个编译问题需要处理。这两个问题都与我之前列出的 JavaScript 代码中的 bug 有关。幸好 TypeScript 不会让我们轻易放弃这个问题,所以让我们先把这些问题解决掉。

首先,我们showAddItemsPrompt在 中调用了updateTestCases,但我们的方法却被调用了showAddItemPrompt。这是一个显而易见的问题,可能是由于拼写错误,或者重命名了现有方法但缺少引用造成的。只需确保名称匹配,就可以轻松解决这个问题。

其次,failTestCase声明一个名为 的变量testCase,然后尝试将其引用为testcase,这根本行不通。这个问题很容易解决,只要确保名称一致即可。

引用我们的编译代码

而且,运行tsc结果没有输出 - 这意味着我们的代码编译没有问题!

最重要的是,因为 Logic.ts 会自动转换为Logic.js我们所引用的文件index.html,这意味着我们甚至不必更新我们的 HTML。

因此,如果我们运行该应用程序,我们可以看到我们可以失败并再次删除测试:

动画 gif 展示了失败、通过和删除新项目的效果

但是等等,代码中不是有三个错误吗?TypeScript 只发现了两个!

是的,但是我们还没有告诉 TypeScript 足够的信息来找到第三个。让我们通过重新启用严格模式来解决这个问题。

严格模式

返回tsconfig.json,设置stricttrue

这会在编译过程中产生大约 16 个错误。绝大多数都是no impicit any,或者 TypeScript 抱怨它不知道对象是什么类型。查看和修复这些问题相当简单,所以我就不赘述了,但如果你感到困惑,可以随时查看我的最终结果。

除此之外,我们还看到一些 TypeScript 指出内容可能为空的情况。这些情况涉及从页面获取 HTML 元素,可以通过类型断言来解决:

const list = <HTMLElement>document.getElementById('listTestCases');

类型断言在这里是可以接受的,因为我们明确选择接受 HTML 元素 ID 更改导致错误的风险,而不是试图以某种方式让应用程序在没有必需用户界面元素的情况下正常运行。在某些情况下,正确的选择是进行空值检查,但在这种情况下,额外的复杂性是不值得的,因为尽早失败可能更有利于可维护性。

删除全局状态

这样我们就剩下 5 个错误,它们都是同一类型:

'this' implicitly has type 'any' because it does not have a type annotation.

TypeScript 告诉我们,它并不乐意用 this 来引用全局作用域中的项。为了解决这个问题(这里没有双关语),我将把状态管理逻辑包装到一个新类中:

这会产生许多编译器错误,因为现在需要引用testManager实例上的方法或传递testManager给其他成员。

这也暴露了一些新问题,包括我多次提到的那个错误。

具体来说,当我们在 中创建测试数据时,buildInitialData我们将 设置id'1'而不是1。更明确地说,idstring而不是number,这意味着它将无法通过任何===检查(尽管==检查仍然会通过)。将属性初始化器更改为使用数字可以解决问题。

_注意:testcases如果我们之前在数组周围声明了类型断言,那么无需提取类就可以发现这个问题。_

其余错误均与处理结果有关,findTestCaseById其结果可以返回 aTestCasenull以其当前形式返回。

在 TypeScript 中,此返回类型可以明确写为TestCase | null。我们可以通过抛出异常来处理这个问题,而不是在未找到测试用例时返回 null ,但我们应该听从 TypeScript 的建议并添加 null 检查。

我略过了不少细节,但如果您对某些内容感到困惑或者想查看最终代码,可以在我的 GitHub 存储库中找到

从 TypeScript 中受益

现在,当我们运行应用程序时,代码可以完美运行

迁移到 TypeScript 后的 JavaScript 应用。所有功能均按预期运行。

不仅如此,编译器本身还确保我们遇到的错误永远不会再发生(如果我们继续遵守规则的话)。

此外,TypeScript 通过强制我们考虑潜在的空值来帮助我们优雅地处理潜在的错误。

后续步骤

如果您有兴趣更深入地了解 TypeScript,请继续关注,因为我打算涵盖更多值得注意的主题,包括:

  • 通过 Linting 查找其他问题
  • 使用 Jest 测试 TypeScript
  • 使用 Prettier 自动格式化代码
  • 将文件捆绑在一起
  • 使用 NPM 和 WebPack 管理复杂的构建过程

如果您想从一个已经为这些内容设置好的新项目开始,我建议您查看GitHub 上的Christoffer Noring 的 TypeScript Playground 存储库。

结束语

最近,很多人攻击 TypeScript,说它碍事、混淆 JavaScript、没有必要等等。当然,对于这种规模的应用程序来说,TypeScript 可能有点大材小用,但我的立场是这样的:

TypeScript 本质上是一张巨大的安全网,你可以在构建 JavaScript 代码时使用它。没错,搭建这张安全网需要付出努力,而且,你可能不需要用它来处理一些琐碎的事情,但如果你正在开发一个大型项目,并且没有足够的测试覆盖率,那么你就需要某种形式的安全网,否则你就会把质量问题转嫁给用户。

在我看来,TypeScript 是一个非常有价值的安全网,它支持现有和未来的单元测试,并允许 QA 专注于业务逻辑错误和可用性,而不是编程错误。

我之前将一个大型 JavaScript 应用程序迁移到 TypeScript,效果非常好。在这个过程中,我解决了大约 10 到 20 个未解决的 Bug,因为 TypeScript 让错误变得非常明显,让人无法忽视。

更好的是,这个过程使得应用程序每次被触碰时发生的错误类型不可能再次发生。

那么,问题是:你的安全网是什么?你真的愿意让语言偏好将你可能忽略的缺陷传递给最终用户吗?

迁移到 TypeScript一文首先出现在Kill All Defects上。

文章来源:https://dev.to/integerman/migration-to-typescript-3bai
PREV
内向者公开演讲的技巧:自信与能力 演讲作为成长机会 重塑演讲体验 登上舞台
NEXT
C# 8 如何帮助提高软件质量