前言
2016 年春天,我拜访了工作在谷歌旧金山办公室的老同事 Evan Martin,并像过去一样询问了他正沉迷于哪个领域。鉴于他每年的答案都十分广泛、不可预知却又十分有趣,所以这些年,我问过他很多次这个问题,他的回答包括但不限于C++ 构建工具、Linux 音频驱动、在线纵横字谜、Emacs 插件。而这一次,Evan 的回答是他正沉迷于 TypeScript 和 Visual Studio Code。我十分惊讶!我曾在不久前听说过 TypeScript,但彼时我对它的了解仅限于它是由微软创造出来的,且误以为它和 .NET 有关。作为一个 Linux 的忠实用户,我实在难以相信 Evan 竟然投向了微软的怀抱。
但在 Evan 向我展示了 Visual Studio Code 和 TypeScript 学习乐园(TypeScript Playground)之后,我立刻改变了我的想法。一切运转得都很快,代码智能使得构建类型系统的心智模型变得很容易。在为 Closure 编译器在 JSDoc 注释中编写了多年的类型标注之后,这给我的感觉是 TypeScript 像是真正工作的类型化 JavaScript。微软已经在 Chromium 之上建立了一个跨平台的文本编辑器?也许这是一种值得学习的语言和工具链。
我当时刚刚加入了 Sidewalk Labs ,正在写我们的第一个 JavaScript。代码库还很小,Evan 和我可以在接下来的几天时间内把它全部转换成 TypeScript。从那以后我就被迷住了。TypeScript 不仅仅是一个类型系统,它还带来了一整套快速易用的语言服务。TypeScript 不仅让 JavaScript 开发更安全,而且让它更有趣!
本书面向的读者群
“Effective”系列丛书旨在成为他们描述主题的“标准第二本书”。如果你有使用 JavaScript 和 TypeScript 的实践经验,那么你将能充分利用有效的TypeScript。我写本书的目的不是教你如何使用 TypeScript 或 JavaScript,而是帮助你从一个初级或中级用户成长成为一个专家。本书中的内容可以帮助你利用 TypeScript 及其生态系统建立工作的心智模型,让你意识到要避免的陷阱,并引导你以最有效的方式使用 TypeScript 的许多功能。参考书会解释一门编程语言可以让你做某些事的五种方法,而 “Effective 系列丛书则会告诉你它们分别是哪五种方法及为什么。
TypeScript 在过去几年内得到了迅速发展,但我希望它已经稳定到足以使本书的内容在今后几年内都能保持有效。本书主要关注语言本身,而不是任何框架或构建工具。你既找不到任何关于如何对 TypeScript 使用 React 或 Angular的示例,也找不到如何配置 TypeScript 以使用 Webpack、Babel 或 Rollup 的示例。本书的建议适合所有的 TypeScript 用户。
我为什么写本书
我刚到谷歌工作时,拿到了《Effective C++(第三版)》。它不像我读过的任何其他编程书。它没有试图让初学者知道如何能够接触到它,也没有试图成为一个完整的语言指南。它没有告诉你 C++ 的不同特性,而是通过几十个简短、具体而又带有具体实例的条款告诉你应该或不应该如何使用它们。
通过阅读这些例子,你能清楚明白地在日常工作中使用这门语言。我以前也编写C++ 程序,但从那之后我开始能够舒服地使用它,并知道如何思考它给予我的选择。后来在读《Effective Java》和《Effective JavaScript》时,它们也带给了我相似的体验。
如果你已经习惯了使用几种不同的编程语言,那么直接跳进一种新的编程语言的奇怪角落,是一种有效的方法,这样可以挑战你的思维模式,了解这门语言的不同之处。我在写本书的过程中收获了大量关于 TypeScript 的知识,我希望你在阅读本书的过程中也能收获同样的体验!
本书结构
本书是由“条款”构成的,每一个条款都是一篇简短的技术文章,为你提供有关TypeScript 的某些方面的具体建议。这些条款按主题分成章节,但你可以随意翻阅你最感兴趣的条款。
每个条款的标题传达了该条款的关键点。在使用 TypeScript 时,这些都是你应该记住的,所以你有必要浏览一下目录,让它们进入你的脑海。例如,如果你正在编写文档,并且有不应该编写类型信息的困扰,那么你将知道去阅读条款30:不要在文档中重复类型信息。
条款里面的文本激发了标题中的建议,并用具体的例子和技术论据来支持它。本书中的几乎每一点都是通过示例代码来演示的。我倾向通过看例子和略读散文来阅读技术书籍,我想你也会做类似的事情。我希望你能读到这篇文章和解释!但是,如果你略过这些例子,你还是能够获得主要的观点的。
阅读完条款后,你应该明白为什么它能帮助你更有效地使用 TypeScript。如果这不适合你的情况,你也会明白其中原因。Scott Meyers——《Effective C++》的作者,给出了一个难忘的例子。Scott 遇到了一个工程师团队,他们写的软件是在导弹上运行的。他们知道他们可以忽略Scott 关于防止资源泄漏的建议,因为当导弹击中目标,硬件爆炸时,他们的程序总是会终止的。我不知道有任何带有 JavaScript 运行时的导弹,但是 James Webb 太空望远镜是有的,所以你永远不会知道你的代码会在哪使用!
最后,每一个条款都以“要记住的事情”结束,其下是从该条款出发总结的几个要点。如果你只是浏览本书,你可以阅读这些,以了解该条款在讲什么,以及你是否想读更多。你还是应该读读这篇文章!但总结也是必要的。
TypeScript 示例代码约定
除非上下文清楚地表明它们采用的是 JSON、GraphQL 或其他某种语言,其余所有代码示例采用的都是 TypeScript 语言。使用TypeScript 的大部分经验都涉及与编辑器的交互,这给输出带来了一些挑战。我已经采用了一些惯例使得这个办法可行。
大多数编辑器都使用弯曲下划线来显示错误。若要查看完整的错误消息,需要将鼠标悬停在带下划线的文本上。为了指示代码示例中的错误,我在错误发生的位置下的注释中使用了波浪线标记:
let str = 'not a number';
let num: number = str;
// ~~~ Type 'string' is not assignable to type 'number'
为了简洁明了,我偶尔会编辑错误消息,但从不删除错误。如果将代码示例复制/ 粘贴到编辑器中,则应能准确地获得错误提示,且不多不少。
为了提示没有错误,我使用 //OK 标记:
let str = 'not a number';
let num: number = str as any; // OK
你应该能够将鼠标悬停在编辑器中的符号上,以查看 TypeScript 认为其类型是什么。为了在文本中指出这一点,我使用了以 “type is” 开头的注释标记:
let v = {str: 'hello', num: 42};
// Type is { str: string; num: number; }
该类型用于行上的第一个符号(在本例中为 v)或函数调用的结果:'four score'.split(' '); // Type is string[]
这与你在编辑器中看到的字符对字符的类型相匹配。在函数调用的情况下,可能需要分配临时变量以查看类型。
我偶尔会引入没有意义的语句来指示特定代码行上的变量类型:
function foo(x: string|string[]) {
if (Array.isArray(x)) {
x; // Type is string[]
} else {
x; // Type is string
}
}
x; 行仅用于演示条件的各个分支中的类型。你不需要(也不应该)在自己的代码中包含这样的语句。
除非上下文中另有说明或明确说明,否则代码示例将使用 strict 标志进行检查。所有示例均使用 TypeScript 3.7.0-beta 进行了验证。
排版约定
本书使用了下述排版约定:
斜体(Italic)
表示新术语、URL、电子邮件地址、文件名和扩展名。
等宽字体(Constant Width)
表示程序列表,以及段落中引用程序的元素,如变量或函数名、数据库、数据类型、环境变量、语句和关键字。
等宽粗体(Constant width bold)
表示用户应逐字输入的命令或其他文本。
等宽斜体(Constant width italic)
表示应替换为用户提供的值或由上下文确定的值的文本。
使用示例代码
补充材料(代码示例、练习等)可从 https://github.com/danvk/effectivetypescript下载。
如果你有技术问题或使用代码示例时遇到问题, 请发送电子邮件至bookquestions@oreilly.com。
本书是来帮助你完成工作的。一般来说,如果本书提供了示例代码,你可以在程序和文档中使用它。你不需要联系我们获得许可,除非你正在复制代码的重要组成部分。例如,编写一个使用本书中几段代码的程序不需要获得许可,而销售或分发O’Reilly 书籍的示例是需要获得许可的;通过引用本书并引用示例代码来回答问题不需要获得许可,而将本书中大量的示例代码合并到产品文档中却是需要获得许可的。
我们不强制要求署名,但如果你这么做,我们深表感激。署名一般包括书名、作者、出版社和国际标准图书编号。例如,“Effective TypeScript by Dan Vanderkam (O’Reilly). Copyright 2020 Dan Vanderkam, 978-1-492-05374-3。”
如果你觉得自身情况不在合理使用或上述允许的范围内,请通过邮件和我们联系,地址是 permissions@oreilly.com。
O’Reilly 在线学习平台(O’Reilly Online Learning)
近40 年来,O’Reilly Media 致力于提供技术和商业培训、知识和卓越见解,来帮助众多公司取得成功。
我们拥有独一无二的专家和革新者组成的庞大网络,他们通过图书、文章、会议和我们的在线学习平台分享他们的知识和经验。O’Reilly 的在线学习平台允许你按需访问现场培训课程、深入的学习路径、交互式编程环境,以及O’Reilly 和200 多家其他出版商提供的大量文本和视频资源。有关的更多信息,请访问http://oreilly.com。
意见和疑问
请把你对本书的意见和疑问发给出版社:
美国:
O’Reilly Media, Inc.
1005 Gravenstein Highway North
Sebastopol, CA 95472
中国:
北京市西城区西直门南大街2号成铭大厦C座807室(100035)
奥莱利技术咨询(北京)有限公司
本书有专属网页,你可以在那儿找到本书的勘误、示例和其他信息。这个网页的地址是 https://oreil.ly/Effective_TypeScript。
如果你对本书有一些评论或技术上的建议, 请发送电子邮件到bookquestions@oreilly.com。
有关我们的书籍、课程、会议和新闻的更多信息,请访问我们的网站:http://www.oreilly.com。
我们的 Facebook : http://facebook.com/oreilly。
我们的 Twitter : http://twitter.com/oreillymedia。
我们的 YouTube: http://www.youtube.com/oreillymedia。
致谢
许多人的帮助使本书的出版成为可能。感谢 Evan Martin 给我介绍TypeScript,并教我如何思考。感谢 Douwe Osinga 把我和 O’Reilly 联系起来并支持这个项目。感谢 Brett Slatkin 提供的结构方面的建议,让我认识到我是可以写一本 Effective 系列丛书的人。感谢 Scott Meyers 提出的这种格式,以及他的《高效的Effective 书籍》博客文章,这篇文章为我提供了必要的指导。感谢我的审校——Rick Battaglin、Ryan Cavanaugh、Boris Cherny、Yakov Fain、Jesse Hallett 和 Jason Killian,感谢所有在 Sidewalk 和我一起学习TypeScript 的同事,感谢在 O’Reilly 帮助本书得以实现的每一个人——Angela Rufino、Jennifer Pollock、Deborah Baker、Nick Adams 和 Jasmine Kwityn。感谢 TypeScript 的纽约团队成员——Jason、Orta、Kirill,以及所有的演讲者。许多条款的灵感皆来自会议上的会谈,如下所示:
• 条款 3 的灵感来自 Evan Martin 的一篇博客,在我第一次学习 TypeScript的时候,我就发现这篇文章特别有启发性。
• 条款 7 的灵感来自 Anders 在 2018 年 TSConf 大会上关于结构类型和keyof 关系的演讲,以及 Jesse Hallett 在 2019 年 4 月纽约 TypeScript 会议上的演讲。
• Basarat 的指南及 DeeV 和 Gpicazo 在 Stack Overflow 的回答对编写条款 9时都是必不可少的。
• 条款 10 建立在《Effective JavaScript》(Addison Wesley)的条款 4 中的类似建议之上。
• 在 2019 年 8 月的纽约 TypeScript 会议上,我被围绕额外类型检查的局限性这一话题所鼓舞,写下了条款11。
• 在 Stack Overflow 上关于“type”与“interface”的几个问题极大地帮助了条款13。Jesse Hallett 提出了关于可扩展性的公式。
• Jacob Baskin 为条款 14 提供了鼓励和早期反馈。
• 条款 19 的灵感来自提交给 Reddit TypeScript 话题的几个代码示例。
• 条款 26 是基于我自己在 Medium 上的文章和我在 2018 年 10 月纽约TypeScript 会议上的演讲。
• 条款 28 基于 Haskell 的一般建议(非法状态不能表示)。法航 447 的故事灵感来自 Jeff Wise 2011 年在《大众机械》上发表的文章。
• 条款 29 基于我在 Mapbox 类型声明中遇到的一个问题。Jason Killian 建议在标题中使用这个短语。
• 条款 36 关于“命名”的建议很常见,但是这个建议是受 Dan North 在《程序员应该知道的97 件事》(O’Reilly)的启发。
• 条款 37 的灵感来自 Jason Killian 在 2017 年 9 月纽约第一次 TypeScript 会议上的演讲。
• 条款 41 基于 TypeScript 2.1 release note。“any 演变”这个术语在TypeScript 编译器本身之外并没有被广泛使用,但是我发现为这个不寻常的模式命名是很有用的。
• 条款 42 的灵感来自 Jesse Hallett 的一篇文章。
• Titian Cernicova Dragomir 对 TypeScript issue #33128 提出的反馈对条款43 很有帮助。
• 条款 44 基于 York Yao 的“类型覆盖”工具所做的工作。我想要这样的一个工具并且它竟然存在!
• 条款 46 基于我在 2017 年 12 月纽约 TypeScript 会议上的演讲。
• 多亏了 David Sheldrick 在 “Artsy” 博客上发表的关于条件类型的文章,这让我对条款50 这个话题大开眼界。
• 条款 51 的灵感来自 Steve Faulkner 即 southpolesteve 在 2019 年 2 月的会议上的演讲。
• 条款 52 基于我自己在 Medium 上的文章和输入检查工具的工作,它最终被打包放入dtslint 里。
• 条款 53 的灵感来自 Kat Busch 在 Medium 上关于 TypeScript 中各种枚举类型的帖子,以及 Boris Cherny 在《Programming TypeScript》(O’Reilly)中关于这个主题的文章。
• 条款 54 的灵感来自我自己和同事在这个话题上的困惑。Anders 对TypeScript 的 PR#12253 给出了明确的解释。
• MDN 文档对于编写条款 55 非常重要。
• 条款 56 大致基于《Effective JavaScript》(Addison-Wesley)的条款 35。
• 第 8 章基于我自己迁移老化动态图库的经验。
我在 Reddit 的 TypeScript 专题下找到了很多优秀的博客文章和访谈,它们同样也帮助促成了本书。我特别感谢在那里提供代码示例的开发人员,这些示例对于帮助初学者理解 TypeScript 使用中面临的常见问题非常重要。感谢Marius Schulz 的每周 TypeScript newsletter。虽然它只是偶尔的每周,但它是一个很好的材料来源和跟上 TypeScript 很好的方式。感谢 Anders、Daniel、Ryan 和微软的整个 TypeScript 团队,他们与我进行会谈,并就问题提供所有反馈。我的大部分问题都是误会,但没有什么比提交一个错误并立即看到Anders Hejlsberg 自己修复它更有成就感的了!最后,感谢 Alex 在我完成整个项目的过程中,无时无刻对我的体谅。