likes
comments
collection
share

TypeScript 学习指南——从 JavaScript 到 TypeScript

作者站长头像
站长
· 阅读数 11

本文选自我在翻译的《Learning TypeScript》,比较长,读完需耐心。

今天的 JavaScript 支持几十年前的浏览器 网络之美

在谈论 TypeScript 之前,我们需要先了解它的来源:JavaScript!

JavaScript 的历史

1995年,Netscape 公司的 Brendan Eich 花了10天时间设计出了易用的JavaScript。从那以后,开发人员一直在取笑它的怪癖和明显的缺点。我将在下一节中介绍其中的一些。

然而,自1995年以来,JavaScript已经有了巨大的发展!它的指导委员会 TC39 发布了 ECMAScript 的新版本, ECMAScript 是JavaScript自 2015 年以来每年基于的语言规范,其中包含了使 JavaScript 与其他现代语言保持一致的新特性。令人印象深刻的是,即使是常规的新语言版本,JavaScript 在不同的环境(包括浏览器、嵌入式应用程序和服务器运行时)中仍然能够保持向后兼容性数十年。

今天,JavaScript 是一门非常灵活的语言,具有很多优点。我们应该意识到,虽然 JavaScript 有它的怪癖,但它也帮助实现了 Web 应用程序和互联网的惊人增长。

给我看完美的编程语言,我会给您展示一种没有用户的语言。 ——安德斯·海尔斯伯格,TSConf 2019

原生 JavaScript 的陷阱

开发人员经常将没有任何重要语言扩展或框架的 JavaScript 称为“原生”:指的是它是熟悉的原始风格。我很快就会讨论为什么 TypeScript 添加了恰到好处的味道来克服这些特定的主要陷阱,但是理解为什么它们会很痛苦是很有用的。所有这些弱点都变得更加明显,项目越大,寿命越长。

昂贵的自由

不幸的是,许多开发人员对 JavaScript 最大的抱怨是它的一个关键特性:JavaScript 几乎没有对如何构建代码进行任何限制。这种自由让用 JavaScript 启动项目变得非常有趣!

然而,当你拥有越来越多的文件时,这种自由的破坏性就变得很明显了。从一些虚构的绘画应用程序中提取以下片段:

function paintPainting(painter, painting) {
  return painter
    .prepare()
    .paint(painting, painter.ownMaterials)
    .finish();
}

在没有任何上下文的情况下阅读该代码,您只能对如何调用 paintPainting 函数有模糊的想法。如果您在周围的代码库中工作过,您可能会记得 painter 应该是某个 getPainter 函数返回的内容。您甚至可以幸运地猜测 painting 是一个字符串。

然而,即使这些假设是正确的,以后对代码的更改也可能使它们失效。也许 painting 从字符串更改为其他数据类型,或者 painter 的一个或多个方法被重命名。

如果其他语言的编译器确定代码可能会崩溃,它们可能会拒绝让您运行代码。动态类型语言(那些运行代码而不检查代码是否可能首先崩溃的语言)则不是这样,比如 JavaScript。

当你想要安全运行代码时,让 JavaScript 变得如此有趣的代码自由变成了真正的痛苦。

松散的文档

JavaScript 语言规范中没有任何东西可以形式化描述代码中的函数参数、函数返回、变量或其他构造。许多开发人员采用了一种称为 JSDoc 的标准来使用块注释来描述函数和变量。JSDoc 标准描述了如何编写直接放置在构造(如函数和变量)上方的文档注释,这些注释以标准方式格式化。下面又是一个断章取义的例子:

/**
* Performs a painter painting a particular painting.
*
* @param {Painting} painter
* @param {string} painting
* @returns {boolean} Whether the painter painted the painting.
*/
function paintPainting(painter, painting) { /* ... */ }

JSDoc 有一些关键问题,通常会让它在大型代码库中使用不愉快:

  • 没有什么能阻止 JSDoc 对代码的描述是错误的。
  • 即使您的 JSDoc 描述以前是正确的,在代码重构期间,也很难找到与您的更改相关的所有现在无效的 JSDoc 注释。
  • 描述复杂对象既笨拙又冗长,需要多个独立的注释来定义类型及其关系。

在十几个文件中维护 JSDoc 注释并不会占用太多时间,但是在数百甚至数千个不断更新的文件中维护 JSDoc注释确实是一件苦差事

较弱的开发人员工具

由于 JavaScript 不提供识别类型的内置方法,并且代码很容易与 JSDoc 注释不同,因此很难自动对代码库进行大量更改或获得有关代码库的见解。JavaScript 开发人员经常惊讶地看到 C# 和 Java等类型化语言中的特性,允许开发人员执行类成员重命名或跳转到声明参数类型的位置。

您可能会抗议现代 IDE(如 VS Code)确实提供了一些开发工具,例如对 JavaScript 的自动重构。没错,但是:它们在后台使用 TypeScript 或类似的许多 JavaScript 功能,并且这些开发工具在大多数 JavaScript 代码中不像在定义良好的 TypeScript 代码中那样可靠或强大。

TypeScript!

TypeScript 于 2010 年代初在微软内部创建,然后在 2012 年发布并开源。其开发的负责人是Anders Hejlsberg,他也以领导流行的 C# 和 Turbo Pascal 语言的开发而闻名。TypeScript 通常被描述为“JavaScript 的超集”或“带有类型的 JavaScript”。但什么 TypeScript呢?

TypeScript 有四个方面:

  • 编程语言 一种语言,包括所有现有的 JavaScript 语法,以及用于定义和使用类型的新 TypeScript 特定语法
  • 类型检查器 一个程序,它接受一组用 JavaScript 和(或) TypeScript 编写的文件,了解创建的所有结构(变量,函数……),并让您知道它是否认为任何设置不正确。
  • 编译器 一个类型检查器程序,报告任何问题,然后输出等效 JavaScript 代码
  • 语言服务 一个程序,使用类型检查器告诉编辑器(如 VS Code)如何为开发人员提供有用的实用程序

从 TypeScript 演练场开始

到目前为止,您已经阅读了大量有关 TypeScript 的信息。让我们开始写吧!

主要的 TypeScript 网站包括一个“演练场”编辑器,网址为。www.typescriptlang.org/play。您可以在主编辑器中键入代码,并看到许多与在完整的 IDE(集成开发环境)本地使用 TypeScript 时相同的编辑器建议。

本书中的大多数片段都特意很小且足够独立,您可以在演练场上键入它们,并对其进行修改以获得乐趣。

TypeScript 实战

看看这个代码片段:

const firstName = "Georgia";
const nameLength = firstName.length();
//    ~~~~~~
// This expression is not callable.

代码是用普通的 JavaScript 语法编写的——我还没有介绍 TypeScript 特定的语法。如果要对此代码运行 TypeScript 类型检查器,它将利用其字符串的 length 属性是数字(而不是函数)的信息来给您注释中所示的错误。

如果您要把这段代码粘贴到演练场或编辑器中,语言服务会告诉您在 length 下面有一些红色波浪线,表示 TypeScript 对您的代码不满意。将鼠标悬停在波浪线代码上,您会看到错误信息(图 1-1)。

TypeScript 学习指南——从 JavaScript 到 TypeScript 图 1-1 TypeScript 报告字符串长度不可调用的错误

当你输入这些错误时,在编辑器中被告知这些简单的错误,这比等待特定的代码行运行并抛出错误要愉快得多。如果您试图用 JavaScript 运行该代码,它会崩溃!

通过限制获得自由

TypeScript 允许我们指定可以为参数和变量提供哪些类型的值。一些开发人员发现,首先必须在代码中明确写出特定区域应该如何工作是有限制性的。

但!我认为,以这种方式被“限制”实际上是一件好事!通过将我们的代码限制为只能以您指定的方式使用,TypeScript 可以让您确信一个代码区域中的更改不会破坏使用它的其他代码区域。

例如,如果您更改了函数所需的参数数量,TypeScript 会在您忘记更新调用该函数的位置时通知您。

在以下示例中,sayMyName 已从接受两个参数更改为接受一个参数,但使用两个字符串对它的调用未更新,因此 TypeScript 认为那是错误的:

// Previously: sayMyName(firstName, lastName) { ...
function sayMyName(fullName) {
 console.log(`You acting kind of shady, ain't callin' me ${fullName}`);
}
sayMyName("Beyoncé", "Knowles");
// ~~~~~~~~~
// Expected 1 argument, but got 2.

该代码将在 JavaScript 中运行而不会崩溃,但它的输出将与预期不同(它不包括“Knowles”):

You acting kind of shady, ain't callin' me Beyoncé

使用错误数量的参数调用函数正是 TypeScript 限制的那种短视的 JavaScript 自由。

精确的文档记录

让我们看一下前面的 paintPainting 函数的 TypeScript 化版本。虽然我还没有详细介绍用于记录类型的 TypeScript 语法的细节,但以下代码片段仍然暗示了 TypeScript 可以记录代码的高精度:

interface Painter {
  finish(): boolean;
  ownMaterials: Material[];
  paint(painting: string, materials: Material[]): boolean;
}
function paintPainting(painter: Painter, painting: string): boolean { /* ... */ }

第一次阅读此代码的 TypeScript 开发人员可以理解 painter 至少具有三个属性,其中两个是方法。通过烘焙语法来描述对象的“形状”,TypeScript 提供了一个出色的、强制执行的系统来描述对象的外观。

更强大的开发人员工具

TypeScript 的类型允许 VS Code 等编辑器更深入地了解您的代码。然后,他们可以使用这些见解在您键入时显示智能建议。这些建议对开发非常有用。

如果您以前使用过 VS Code 编写 JavaScript,您可能已经注意到,当您使用内置类型的对象(如字符串)编写代码时,它建议使用“自动完成”。例如,如果您开始键入已知字符串的成员,TypeScript 可以建议字符串的所有成员(图 1-2)。

TypeScript 学习指南——从 JavaScript 到 TypeScript 图 1-2 TypeScript 在 JavaScript 中为字符串提供自动完成建议

当您添加 TypeScript 的类型检查器来理解代码时,它甚至可以为您提供这些有用的建议,即使是您编写的代码。在 paintPainting 函数中键入 Painter ,TypeScript 将知道 painter 参数的类型为 Painter,而 Painter 类型具有以下成员(图 1-3)。

TypeScript 学习指南——从 JavaScript 到 TypeScript 图 1-3 TypeScript 在 JavaScript 中为字符串提供自动完成建议

牛!我将在第 12 章 “使用 IDE 功能”中介绍大量其他有用的编辑器功能。

编译语法

TypeScript 的编译器允许我们输入 TypeScript 语法,对其进行类型检查,并发出生成的 JavaScript。为了方便起见,编译器还可以采用现代 JavaScript 语法并将其编译成较旧的 ECMAScript 等效项。 如果要将此 TypeScript 代码粘贴到 Playground 中:

const artist = "Augusta Savage";
console.log({ artist });

Playground 会在屏幕右侧显示和编译器输出等效的JavaScript(图 1-4)。

TypeScript 学习指南——从 JavaScript 到 TypeScript 图 1-4 TypeScript Playground 将 TypeScript 代码编译为等效的 JavaScript

TypeScript Playground 是一个很好的工具,用于显示源 TypeScript 如何成为输出 JavaScript。

许多 JavaScript 项目使用专用的转译器,例如 Babel(babeljs.io)而不是 TypeScript 自己的将源代码转换为可运行的 JavaScript。您可以在以下位置找到常见项目启动器的列表learningtypescript.com/starters.

从本地开始

只要安装了 Node.js,您就可以在计算机上运行 TypeScript。若要全局安装最新版本的 TypeScript,请运行以下命令:

npm i -g typescript

现在,您将能够使用 tsc(TypeScript Compiler)命令在命令行上运行 TypeScript。尝试使用 --version 标志,以确保它设置正确:

tsc --version

它应该打印出类似 Version X.Y.Z 的内容 — 无论您在安装 TypeScript 时处于哪个最新版本:

tsc --version Version 4.7.2

本地运行

现在 TypeScript 已安装,让我们在本地设置一个文件夹以在代码上运行 Type-Script。在计算机上的某个位置创建一个文件夹,然后运行以下命令以创建新的 tsconfig.json 配置文件:

tsc --init

tsconfig.json 文件声明 TypeScript 在分析代码时使用的设置。在本书中,该文件中的大多数选项都与您无关(在编程中有很多不常见的边缘情况,语言需要考虑!)。我将在第 13 章 “配置选项”中介绍它们。重要的功能是,现在您可以运行 tsc 来告诉 TypeScript 编译该文件夹中的所有文件,TypeScript 将引用该 tsconfig.json 以获取任何配置选项。

尝试添加一个名为 index.ts 的文件,其中包含以下内容:

console.blub("Nothing is worth more than laughter.");

然后,运行 tsc 并为其提供该 index.ts 文件的名称:

tsc index.ts

您应该会收到一个大致如下所示的错误:

index.ts:1:9 - error TS2339: Property 'blub' does not exist on type 'Console'.

1 console.blub(“Nothing is worth more than laughter.”);
~

Found 1 error.

事实上,console 上不存在 blub。我在想什么?

在修复代码以安抚 TypeScript 之前,请注意 tsc 为您创建了一个 index.js 其中包含 console.blub 的内容。

这是一个重要的概念:即使我们的代码中存在类型错误,语法仍然完全有效。无论任何类型错误,TypeScript 编译器仍将从输入文件生成 JavaScript。

更正 index.ts 中的代码以调用 console.log 并再次运行 tsc。您的终端中应该没有错误,index.js 文件现在应该包含更新的输出代码:

console.log("Nothing is worth more than laughter.");

我强烈建议您在阅读在阅读的时候玩一下书中的代码片段,无论是在演练场上还是在支持 TypeScript 的编辑器中,这意味着它会为您运行 TypeScript 语言服务。小型独立练习以及大型项目也可用于帮助您练习所学知识 https://learningtypescript.com

编辑器功能

创建 tsconfig.json 文件的另一个好处是,当编辑器打开到特定文件夹时,他们现在可以将该文件夹识别为 TypeScript 项目。例如,如果在文件夹中打开 VS Code,则它用于分析 TypeScript 代码的设置将遵循该文件夹的 tsconfig.json 中的任何内容。

作为练习,请返回本章中的代码片段,并在编辑器中键入它们。您应该会看到下拉列表,建议您在键入名称时完成名称,尤其是对于成员,例如 console 上的 log

非常令人兴奋:您正在使用 TypeScript 语言服务来帮助自己编写代码!您正在成为 TypeScript 开发人员的路上!

VS Code 具有出色的 TypeScript 支持,并且本身内置于 TypeScript 中。您不一定要将它用于 TypeScript——几乎所有现代编辑器都有很好的 TypeScript 支持,无论是内置的还是通过插件提供的——但我确实推荐您至少在阅读本书时尝试 TypeScript。如果您确实使用其它编辑器,我还建议您启用其 TypeScript 支持。我将在第 12 章 “使用 IDE 功能”中更深入地介绍编辑器功能。

TypeScript 不是什么

既然您已经看到了 TypeScript 有多棒,我必须警告您一些限制。每个工具在某些领域都表现出色,但在其他领域也有局限性。

错误代码的补救措施

TypeScript 可以帮助您构建您的 JavaScript,但除了强制类型安全之外,它不会强制任何关于结构应该是什么样子的意见。

真好!

TypeScript 是一种每个人都应该能够使用的语言,而不是一个有目标受众的固执己见的框架。您可以使用 JavaScript 中习惯的任何架构模式编写代码,TypeScript 将支持它们。

如果有人试图告诉您 TypeScript 强迫您使用类,或者让您很难写出好的代码,或者任何代码风格的抱怨,请严厉地看他们,告诉他们拿起一份 Learning TypeScript。TypeScript 不会强制执行代码样式的意见,例如是否使用类或函数,也不与任何特定的应用程序框架(Angular、React 等)相关联。

JavaScript 的扩展(主要的)

TypeScript 的设计目标明确指出它应该:

  • 与当前和未来的 ECMAScript 提案保持一致
  • 保留所有 JavaScript 代码的运行时行为

TypeScript 根本不会尝试改变 JavaScript 的工作方式。它的创建者非常努力地避免添加新的代码功能,这些代码功能会增加 JavaScript 或与 JavaScript 冲突。这样的任务是 TC39 的领域,该技术委员会负责 ECMAScript 本身。

TypeScript 中有一些较旧的功能是多年前添加的,以反映 JavaScript 代码中的常见用例。其中大多数功能要么相对不常见,要么已经失宠,仅在第 14 章“语法扩展”中简要介绍。我建议在大多数情况下远离它们。

截至 2022 年,TC39 正在研究向 JavaScript 添加类型注释的语法。最新的提案将它们作为一种注释形式,不会影响运行时的代码,并且仅用于开发时系统,如 TypeScript。在将类型注释或等效物添加到 JavaScript 之前还需要很多年,所以它们不会在本书的其他地方提及。

比 JavaScript 慢

有时在互联网上,您可能会听到一些固执己见的开发人员抱怨 TypeScript 在运行时比 JavaScript 慢。这种说法通常是不准确的,具有误导性。TypeScript 对代码所做的唯一更改是,如果您要求它将代码编译到早期版本的 JavaScript 以支持较旧的运行时环境,例如 Internet Explorer 11。许多生产框架根本不使用 TypeScript 的编译器,而是使用单独的工具进行转译(部分将源代码从一种编程语言转换为另一种编程语言的编译),TypeScript 仅用于类型检查。

但是,TypeScript 确实会为构建代码增加一些时间。TypeScript 代码必须先编译为 JavaScript,然后大多数环境(如浏览器和 Node.js)才能运行它。大多数构建管道通常设置为对性能的影响可以忽略不计,并且较慢的 TypeScript 功能(如分析代码以查找可能的错误)与生成可运行的应用程序代码文件分开完成。

即使是看似允许直接运行 TypeScript 代码的项目,例如 ts-node 和 Deno,它们本身也会在运行 TypeScript 代码之前在内部将 TypeScript 代码转换为 JavaScript。

完成演化

Web 远未完成演化,因此 TypeScript 也远未完成。TypeScript 语言不断收到错误修复和功能添加,以满足 Web 社区不断变化的需求。您将在本书中学到的 TypeScript 基本原则将保持不变,但错误消息、更高级的功能和编辑器集成将随着时间的推移而改进。

事实上,虽然这本书的这个版本是用 TypeScript 版本 4.7.2 作为最新版本出版的,但当您开始阅读它时,我们可以确定一个更新的版本已经发布。本书中的一些 TypeScript 错误消息甚至可能已经过时了!

总结

在本章中,您将了解 JavaScript 的一些主要弱点的上下文,TypeScript 在哪里发挥作用,以及如何开始使用 TypeScript:

  • JavaScript 简史
  • JavaScript 的陷阱:昂贵的自由,松散的文档和较弱的开发人员工具
  • 什么是 TypeScript:编程语言,类型检查器,编译器和语言服务
  • TypeScript 的优势:通过限制获得自由、精确的文档和更强大的开发人员工具
  • 开始在 TypeScript Playground 上和本地计算机上编写 TypeScript 代码
  • TypeScript 不是什么:对错误代码的补救措施,对 JavaScript 的扩展(主要的),比 JavaScript 慢,或者完成演化

现在您已经读完了这一章,您最好练习一下学到的东西 https://learningtypescript.com/from-javascript-to-typescript


如果您发现运行 TypeScript 编译器的错误,会发生什么? 您最好去抓住他们!