文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

Angular 2 使用 TypeScript 来开发。在这篇文章中我将谈到为我们为何做了这样一个决定。我也将分享我使用 TypeScript 的经验:它是如何影响我代码的编写和代码的重构。

我喜爱 TypeScript,但你不需要勉强

尽管 Angular 2 默认使用 TypeScript 进行开发,你也不必用它来编写 Angular 2 程序。这个框架同样支持 ES5,ES6 和 Dart。

TypeScript 拥有很好的工具

TypeScript 最大的卖点在于它的工具。它提供了先进的自动补全功能,导航,以及重构。有这样的工具几乎是开发大型项目的必要条件。没了这些工具,修改代码的恐惧将会导致该代码库在一个半只读(semi-read-only)状态, 并且使大规模重构变得极具风险,同时消耗巨大资金。

第 1 段(可获 2 积分)

TypeScript 不是唯一的可以编译为 JavaScript 的类型化语言。有一些具有强类型系统的编程语言在理论上可以提供绝对惊人的工具。但是在实践中发现它们大多数都仅有编译器而没有其他的工具。这是因为构建丰富的开发工具从第一天起就成为了 TypeScript 团队的明确目标。这也是为什么他们构建了编程语言服务,使得编辑器可以提供类型检查以及自动补全的功能。如果你想知道为什么有那么多的编辑器都对 TypeScript 有极好的支持,答案就是因为 TypeScript 提供了编程语言服务。

第 2 段(可获 2 积分)

事实上,可靠的智能提示和基本重构功能 (例如重命名一个符号)对编写代码尤其是重构代码的过程有巨大的影响。尽管这种影响无法量化,但是在我看来之前需要几天才能完成的重构工作现在不到一天就可以完成。

虽然 TypeScript 大大提高了代码的编辑体验,但是它使开发设置更复杂,尤其是相对于在一个页面中删除一段ES5脚本。还有,你不能使用 JavaScript 源代码分析工具(例如 JSHint),不过通常都会有合适的替代品。

第 3 段(可获 2 积分)

TypeScript 是 JavaScript 的超集

由于 TypeScript 是 JavaScript 的超集,所以你从 JavaScript 迁移到 TypeScript 不需要经过大改写。你可以慢慢的、一次一个模块的迁移。

随便挑选一个模块,修改文件扩展名 .js 为 .ts,然后逐步添加类型注释。当你完成了这个模块,再选择下一个。一旦整个代码库都被类型化,你就可以开始调整编译器设置,使其对代码的检查更加严格。

这个过程可能需要一些时间, 但它对于 Angular 2 来说并不是一个大问题,虽然我们正在迁移至 TypeScript 的过程中。在逐渐过渡的过程中,我们仍然可以维持开发新的功能和修复 bug。

第 4 段(可获 2 积分)

TypeScript 使抽象概念明确

一个好的设计在于定义良好的接口。支持接口的语言使得表达想法变得更加容易。

举个栗子,想象一个购书的程序,完成一次购买操作可以是一个注册的用户在界面上点击的结果,也可以由外部系统通过一系列 API 来完成。

正如你所看到的,这两个类都扮演着买家(Purchaser)的角色。尽管这个角色在应用程序中非常重要,但是买家(Purchaser)概念在代码中没有被明确的表达出来。不存在一个叫做 purchaser.js 的文件。结果就是,可能会发生有人修改代码时忽略了这个角色的存在。

第 5 段(可获 2 积分)
function processPurchase(purchaser, details){ } 
class User { } 
class ExternalSystem { }

仅仅通过查看代码来辨别哪些对象扮演着买家(Purchaser)的角色以及这一角色可以执行哪些方法是非常困难的。我们无法确定,也不能从工具中获取到足够的帮助。我们必须自己来推断这一信息,这个过程是非常缓慢且容易出错的。

现在与上文相比,我们明确地定义了买家(Purchaser)这一接口。

interface Purchaser {id: int; bankAccount: Account;} 
class User implements Purchaser {} 
class ExternalSystem implements Purchaser {}
第 6 段(可获 2 积分)

类型化的版本清晰地声明了买家(Purchaser)接口,而 User 和 ExternalSystem 类都实现了这一接口。因此 TypeScript 的接口允许我们定义抽象概念/协议/角色。

重要的是要意识到 TypeScript 并不强迫我们引入额外的抽象概念。买家(Purchaser)这个抽象概念本就存在于 JavaScript 版本的代码中,只是 它没有被明确定义出来。

在一个静态类型的编程语言中,使用接口来定义子系统之间的界限。由于 JavaScript 缺少接口这一概念,因此界限在纯 JavaScript 中表达不明确。因为不能清楚地看到界限,开发者开始依赖具体类型而不是抽象接口,导致了紧密耦合。

第 7 段(可获 2 积分)

我过去使用 Angular 2 以及后来我们过渡到 TypeScript 的经验使得我更加确信这一点。定义接口促使我去思考 API 界限问题,帮助我定义子系统的公共接口,同时公开其中附带的耦合。

TypeScript 使代码更易阅读和理解

是的,我知道这样说似乎并不直观。让我通过一个例子来说明我的意思。让我们来看这个方法 jQuery.ajax()。我们能从他的特征获得哪些类型的信息?

jQuery.ajax(url, settings)

我们唯一能肯定的是,这个函数接收两个参数。我们可以猜到参数的类型。可能第一个参数是一个字符串,而第二个是一个配置项对象。但这只是猜测,不一定正确。我们不知道这个 settings 对象有哪些选项 (包括它们的名字和它们的类型),也不知道方法的返回值。

第 8 段(可获 2 积分)

在没有查看源码或者文档的情况下,我们无法知道应该怎么调用这个方法。查看源代码并不是一个好的选择—— 函数和类的关键在于不知道他们是如何实现的也能够使用它们。换句话说,我们应该依赖的是它们的接口,而不是它们的实现。虽然我们可以通过查看文档,但这并不是最好的开发者体验 ——这需要额外的时间,而且文档往往是过时的。

所以即使 jQuery.ajax(url, settings) 很容易阅读 ,但是想要理解如何去调用这个方法,我们需要去查阅它的代码实现或者相应的文档。

第 9 段(可获 2 积分)

现在,让我们和类型化版本的代码来对比一下。

ajax(url: string, settings?: JQueryAjaxSettings): JQueryXHR; 
interface JQueryAjaxSettings { 
  async?: boolean; 
  cache?: boolean; 
  contentType?: any; 
  headers?: { [key: string]: any; }; 
  //... 
} 
interface JQueryXHR { 
  responseJSON?: any; //... 
}

它向我们提供了很多信息。

  • 这个方法的第一个参数是一个字符串。
  • settings 参数是可选的。我们可以看到所有可以传递到函数的选项,不仅仅是它们的名字,还有类型。
  • 这个方法返回一个 JQueryXHR 对象,我们可以看到它的属性和功能。
第 10 段(可获 2 积分)

以类型化的语法写出的代码无疑要比没有类型化的长,但是 :string,:JQueryAjaxSettings 以及 JQueryXHR 使得代码不显得杂乱。它们是非常重要的,可以用来代替文档提高代码的可理解性。我们不需要深入了解代码的实现,也不需要去阅读文档,就可以更更好地理解代码。我的个人经验是,我可以更快地阅读类型化的代码,因为类型化提供了更多的上下文来理解代码。如果有读者找到关于类型化影响代码可读性的研究,请在评论中找出。

第 11 段(可获 2 积分)

相比于编译为JavaScript的其他语言来说,TypeScript的一个不同之处在于它的类型注解都是可选的,而jQuery.ajax(url, settings)则总是有效的。所以与其说TypeScript是一个开关,不如说它是一个表盘。当你发现没有类型注解更容易阅读和理解代码的时候,就不要使用它了。只在他们添加值的时候使用它。

TypeScript会限制表达吗?

动态类型语言有较差的实用性,但是他们是更具可扩展性以及有更好的表达力。我想TypeScript使你的代码更加中规总矩,不过这比人们想象的程度要小得多。让我来告诉你我的意思。假设我们使用ImmutableJS来定义一个Person record(个人记录).

第 12 段(可获 2 积分)
const PersonRecord = Record({name:null, age:null}); 
function createPerson(name, age) { 
  return new PersonRecord({name, age}); 
} 
const p = createPerson("Jim", 44); 
expect(p.name).toEqual("Jim");

我们怎么得到一条记录呢?让我们先定义一个Person接口:

interface Person { name: string, age: number };

如果我们试着像下面这样写:

function createPerson(name: string, age: number): Person { 
  return new PersonRecord({name, age}); 
}

TypeScript编译器会报错。 它不知道PersonRecord实际上与Person兼容,因为PersonRecord是反射创建的。 有一些FP背景的人可能会说:“要是TypeScript有依赖类型就好了!”但它没有。 TypeScript的类型系统不是最先进的。 但它的目标是不同的。 这里不是要证明程序100%正确。 而是关于给你更多的信息和提供更多的工具。 因此,当类型系统不够灵活时,可以采取快捷方式。 所以我们只需要转换一下创建的记录,如下:

第 13 段(可获 2 积分)
function createPerson(name: string, age: number): Person { 
  return <any>new PersonRecord({name, age}); 
}

类型化的例子:

interface Person { name: string, age: number }; 
const PersonRecord = Record({name:null, age:null}); 
function createPerson(name: string, age: number): Person { 
  return <any>new PersonRecord({name, age}); 
} 
const p = createPerson("Jim", 44); 
expect(p.name).toEqual("Jim");

之所以上述代码能够运行,是因为类型系统是结构性的。只要创建的对象拥有正确的字段——name和age——就没有问题。

你需要拥有这样的心态,当你使用TyepScript工作时,采取快捷方式是没问题的。只有这样你会发现使用这种语言是有趣的。比如,不要试图向一些独特的元程序代码添加类型—— 你最可能无法静态地表达它。围绕代码为所有东西设置类型,并告知类型系统忽略独特的小部分代码。在这种情况下,你就不会丧失很多的表达,并且你代码的大部分都能保持可维护、可分析的。

第 14 段(可获 2 积分)

这类似于试图实现100%单元测试代码覆盖。然而,实现95%通常并没有那么难,实现100%则具有挑战性,并且可能会负面地影响你的应用构架。

可选类型系统同样保留了JavaScript开发流程。你应用的代码库大部分可以是“蹩脚的”,但是你仍然可以运行它。TypeScript仍可以编译成JavaScript,甚至当类型检测工具提醒问题时。这在开发过程中是非常有用的。

为什么TypeScript?

如今,前端开发工具有很多选择:ES5,ES6(Babel),TypeScript,Dart,PureScript,,Elm等等。那么为什么是TypeScript呢?

第 15 段(可获 2 积分)

让我们从ES5说起。与TypeScript相比,ES5有一个非常大的优势:ES5不需要中间编译器。 这样你就可以保持代码构建很简单。你不需要设置文件监控器,编译代码,生成源映射。ES5写的代码就这样运行了。

ES6需要一个中间编译器,所以代码构建与TypeScript相差不大。但是,ES6是标准,这意味着所有的编辑器以及构建工具,要么支持了ES6,要么将会支持ES6。在这方面的争论一直很小,因为目前大部分编辑器很好地支持TypeScript。

第 16 段(可获 2 积分)

Elm和PureScript是强大的类型系统的优雅语言,关于程序方面可以举出很多例子超过TypeSript。Elm和PureScript编写的代码比用ES5编写类似的代码要精炼许多。

上述选择每一种都有优缺点,但我认为TypeScript恰好处于一种平衡的位置,致使它对大部分项目而言是个很好的选择。TypeScript承载了一个好的类型系统语言95%的优点,并且能够将其应用到JavaScript生态系统。 你仍然会感到你再写ES6:你依旧使用同样的标准库,同样的其它第三方库,同样的术语,以及许多同样的工具(如谷歌开发者工具)。TypeScript让你拥有很多,而又无需强行你离开JavaScript生态系统。 

第 17 段(可获 2 积分)

文章评论

访客
:smiling_imp:
访客
正在用ng2,做单页面应用超级强大,用起来感觉比react舒服
南京访客
js越来越像java,java在努力变成js,各自搞好自己的不就行了么,为啥非要串呢
深圳访客
:thumbsup: