Angular 2 与 TypeScript 概览

阅读数:10894 2016 年 6 月 22 日

话题:JavaJavaScript语言 & 开发架构

迄今为止,在创建 Web 应用方面,AngularJS 是当前最为流行的 JavaScript 框架。如今,Angular 2 和 TypeScript 通过一种非常类似于 Java 8 的语法,使真正面向对象的 Web 开发成为了主流。

据 Google 的工程主管 Brad Green 介绍,有 130 万开发人员在使用 AngularJS 并且 30 万开发人员已经使用了即将发布的 Angular 2。在使用了 Angular 2 近十个月后,我相信它对 JavaScript 社区的影响就像当年 Spring 框架对 Java 的影响那样深远。

在本文中,我们将会在宏观层面上概要阐述 Angular 2 框架。

在 2014 年底,Google 宣布 Angular 2 将会对 AngularJS 进行完全地重写,他们甚至还创建了一门新的语言,名为“AtScript”,他们本来希望使用这门语言来编写 Angular 2 应用。

但是,随后 Microsoft 同意在它们的 TypeScript 语言(JavaScript 的一个严格超集)上添加对装饰符(decorator,又称为注解)的支持,所以,它就成为了开发 Angular 2 框架本身所使用的语言,并且还是使用 AngularJS 框架开发应用的推荐语言。

另外,我们还可以使用 JavaScript(ECMAScript 5 和 6 均可)和 Dart 来编写 Angular 2 应用。

除此之外,Angular 团队还集成了 Microsoft 的另外一个产品到 Angular 2 框架之中,这就是反应型 JavaScript 扩展(reactive JavaScript extension)的 RxJS 库。

Angular 2 并不是一个 MVC 框架,而是基于组件(component)的框架。在 Angular 2 中,应用是松耦合组件所组成的树。

例如,如下的截屏中展现了一个简单的在线拍卖应用的首页面,它最初的原型是由一组 Navbar、Search、Carousel、Product 和 Footer 组件所构成的。

按照上面的图片所示,我们渲染了三个 Product 组件。自动渲染是通过将模板与服务器端获取到的组件数组进行绑定来完成的。每个产品的名称都会是一个链接,指向相关产品的详情页面。因为我们想把这个拍卖应用设计为单页应用(single page application,SPA),所以我们不希望刷新整个页面来展现产品详情。我们会重用当前轮播(carousel)和产品列表已经占据的区域,所以它会渲染产品的详情,同时保持页面的其他内容不变。这项任务通过几个简单的步骤就能完成:

  1. 使用 Angular 的router-outlet指令,它允许我们将当前轮播和产品列表占据的区域声明为 <router-outlet>,这样的话,它就能基于用户的导航变换内容;
  2. 将 Carousel 和 Product 封装到 Home 组件中;
  3. 创建一个新的 ProductDetail 组件;
  4. 配置 Angular 的 Router 在特定的 <router-outlet> 区域要么显示 Home 组件,要么显示 ProductDetail 组件。

关于组件,我们已经讨论了很多,但是到目前为止,还没有对其进行定义。在 TypeScript 中,组件就是带有 @Component 的简单类:

@Component({
  selector: 'auction-home',
  template: `
    HTML 或其他标记内联在此处 
  `
})
export default class HomeComponent {

 // 应用逻辑放在此处 
} 

@Component 注解用来定义组件及其相关的元数据。在本例中,selector属性的值指明了要展现本组件的 HTML 标签名称。template属性是一个 HTML(或其他)标记的占位符。

回到我们的拍卖应用首页,顶层 ApplicationComponent 组件的模板可能会如下所示:

这个模板是由标准的和自定义的 HTML 标签所组成的,自定义标签代表了对应的组件。在本例中,我们使用的是内联 HTML。如果你更喜欢将标签存储在单独的文件中的话(比如在application.html文件中),那么我们将会使用 templateURL 属性来代替 template,ApplicationComponent 的代码将会看起来如下所示:

import {Component} from 'angular2/core';
import {Route, RouteConfig, RouterOutlet} from 'angular2/router';
import HomeComponent from '../home/home';
import NavbarComponent from '../navbar/navbar';
import FooterComponent from '../footer/footer';
import SearchComponent from '../search/search';
import ProductDetailComponent from "../product-detail/product-detail";

@Component({
  selector: 'auction-application',
  templateUrl: 'app/components/application/application.html',
  directives: [
    RouterOutlet,
    NavbarComponent,
    FooterComponent,
    SearchComponent,
    HomeComponent
  ]
})
@RouteConfig([
  {path: '/', component: HomeComponent, as: 'Home'},
  {path: '/products/:id', component: ProductDetailComponent, as: 'ProductDetail'}
])
export default class ApplicationComponent {} 

ApplicationComponent 类使用了 @Component 和 @RouteConfig(对于依赖 URL 的内容)注解。selector 属性的值将会用来指定用户定义的 HTML 标签 <auction-application>。templateURL 属性指定了标记所在的位置。directives 区域包含了 RouterOutlet 以及所有的子组件。

@RouteConfig 注解为客户端导航配置了两个route

  • 对于名为 Home 的 route,其内容将会由 HomeComponent 来渲染,并且映射到了 URL 片段“/”。
  • 对于名为 ProductDetail 的 route,其内容将会由 ProductDetailComponent 来渲染,并且映射到了 URL 片段“/product:id”。

当用户点击一个特定产品的标题时,默认的 Home route 的内容将会替换为 ProductDetail route 的内容,它会提供参数 id 的值并将产品的详情展现在 <router-outlet> 区域。例如,导航至 ProductDetail route 的链接会将产品 id 的值 1234 作为参数,它看起来会如下所示:

<a [routerLink]="['/ProductDetail', {'prodId': 1234}]">{{ product.id }}</a>

依赖注入

组件使用服务(service)来实现业务逻辑。服务是由 Angular 实例化并注入到组件中的类。

export class ProductService {
  products: Product[] = [];
  getProducts(): Array {
    // 获取产品的代码放在这里 
    return products;
  }
} 

现在,如果在 HomeComponent 的构造器中指定一个 ProductService 类型的参数,那么将会自动实例化该服务并将其注入到组件中:

@Component{
 ...
}
export default class HomeComponent {
  products: Product[] = [];

  constructor(productService: ProductService) {
    this.products = productService.getProducts();
  }
}

Angular 的依赖注入模块是很灵活的,它很易于使用,因为对象只能通过构造器来实现注入。注射器(injector)形成了层级的结构(每个组件都会有一个注射器),可注入的对象并不一定必须要在应用级别保持单例,不过,这是 Spring 默认的做法。

跨组件通信

组件通信能够也应该按照一种松耦合的方式来实现。组件可以声明输入和输出属性。要将数据从父组件传递到子组件的话,父组件需要将值绑定到子组件的输入属性上。子组件不需要关心谁来提供值,它只需要知道如何处理这些值即可。

如果某个组件需要将数据传递到外部世界,那么它可以通过输出属性发布事件。将事件发布给谁呢?这就不是组件的业务所要关心的了。如果对其感兴趣的话,可以创建一个针对自定义组件事件的监听器。

这种机制允许我们将组件视为黑盒,它能够获取值或将数据发送出来。我最近录制了一个简短的视频,你可能会发现它有一定的用处,它阐述了在 Angular 2 中,Mediator 设计模式的一种实现。

为何采用 TypeScript

TypeScript 是 JavaScript 的超集,但是它类似于 Java,允许我们定义新的类型。定义变量的时候会使用类型而不是泛指的var,这就为新工具的支持打开了一道门,你会发现这些工具将会带来极大的生产效率提升。TypeScript 自带了一个静态代码分析器,我们还可以在能够感知 TypeScript 的 IDE(WebStorm/IntelliJ Idea、Visual Studio Code、Sublime Text 等)中,直接进入到代码之中,它们能够基于上下文,为我们提供帮助,会指导我们使用对象中可用的方法或函数参数的类型。如果你不小心使用了错误的类型,IDE 将会高亮显示错误的代码。你可以参考这里了解 WebStorm 是如何支持 TypeScript 的。

即便你的 TypeScript 应用用到了 JavaScript 编写的第三方库,你也可以安装一个类型定义文件(扩展名为.d.ts),这个文件包含了这个库的类型声明。针对上百个流行的 JavaScript 库的类型定义都可以免费获取,我们能够使用Typings非常容易地安装它们,Typings 是一个 TypeScript 的定义管理器(TypeScript Definition Manager)。假设你想要在 TypeScript 代码中使用 jQuery(使用 JavaScript 所编写的),针对 jQuery 的类型定义文件将会包含所有 jQuery API 的声明(及类型),所以 IDE 能够提示可以使用哪些类型,或者高亮显示有错误的代码。

性能与渲染

渲染性能在 Angular 2 中得到了充分地提升。最为重要的是,渲染模块位于一个独立的模块之中,这样的话,就允许我们将大量计算(computation-heavy)的代码在一个 worker 线程中运行。你可以访问Repaint Rate Challenge Web 站点来比较各个框架的渲染性能。对于大数据量且数据持续变化的表格,你将会感受到它的渲染是非常迅速的。运行名为“DBMON Angular 2.0 Beta - Web Workers”的测试,这是一个大数据量的表格并且数据会持续刷新(在一个单独的线程中),浏览器对它的重绘极其迅速。

如果你要问 Angular 2 区别于其他框架的特性是什么的话,在我的列表中第一项就会是这个用于模板渲染的独立模块和 zones:

  • 我们将组件的 UI 声明在独立的模板中,并且会由独立的渲染器来进行处理,这样在这方面就有了一个新的机会,这个机会所涵盖的范围从优化和预编译模板,到为不同的设备创建并渲染模板;
  • 模块 zone.js 会监控应用中的变化,并确定在何时更新每个组件的 UI。每个组件中 UI 的重绘都是通过异步事件触发的,它运行起来会非常快。

注意

对于大多数的应用来说,你并不需要了解 zone.js 的内部原理,但是,如果你正在为一个复杂的应用进行 UI 渲染的优化,那么抽出一点时间研究一下 zone 的内部工作原理对你还是很有好处的。

通过将渲染引擎放到一个单独的模块中,第三方的贡献者就可以将默认的 DOM 渲染器替换为其他的渲染器,从而用于非基于浏览器的平台。例如,这允许我们跨设备重用应用程序的代码,针对移动设备的 UI 渲染器可以使用原生的组件。TypeScript 类的代码会保持相同,但是 @Component 注解的内容将会包含 XML 或其他用于渲染原生组件的语言。在NativeScript 框架中已经实现了自定义的 Angular 2 渲染器,该框架可以作为连接 JavaScript 和原生 iOS 及 Android UI 组件的桥梁。借助 NativeScript,你可以重用组件的代码,只需将模板中的 HTML 替换为 XML 即可。还有自定义的 UI 渲染器允许我们将 Angular 2 与 React Native组合在一起使用,它是为 iOS 和 Android 创建原生(非混合)UI 的另一可选方案。

工具

尽管 Angular 2 应用的语法和架构都比 AngularJS 1.X 容易理解得多,但是它的工具会稍微复杂一些。这也并不令人惊讶,毕竟我们使用一门语言来编写代码,却需要使用另外一门语言来进行部署,因为所有的内容都需要编译为 JavaScript。

Angular CLI项目目前正在进行中,它承诺会提供一个命令行接口,大幅度简化各种流程,涵盖的范围从初始的项目创建到生产环境的部署。

应用调试可以在 IDE 中进行,也可以在浏览器中进行。我们使用 Chrome Developer Tools 来进行调试。所生成的代码映射能够让我们调试时使用 TypeScript 代码,而浏览器中运行的是 JavaScript。如果你更愿意调试 JavaScript 的话,这也是可行的,因为 TypeScript transpiler 所生成的 JavaScript 是适于人类阅读的。

测试和部署

Angular 2 自带了一个测试库,它能够让我们按照 BDD 的格式编写单元测试。目前,它只支持 Jasmine 框架,不过对其他框架的支持正在实现之中。我们使用了 Karma test runner,它能够针对各种浏览器运行测试。

借助 Protractor 框架,我们能够为应用编写端到端的测试。如果你在开发模式下,在加载一个简单应用的同时监控网络的话,你会发现浏览器的下载超过了 5Mb(其中的一半是模块加载器所使用的 TypeScript 编译器,SystemJS)。但是运行部署和优化的脚本(我们使用的是 Webpack bundler)之后,小型应用的规模能够减小到 160K(包括 Angular 2)框架。我们正在热切期待 Angular CLI 会如何实现生产环境的打包。Angular 团队在做一个离线模板编译功能,它能够将框架的大小减至 50Kb。

UI 组件库

在撰写本文之时,有多个 UI 组件库是可以和 Angular 2 应用组合在一起使用的:

  • PrimeNG——由 PrimeFaces(与 JavaServer Faces 框架协同使用的一个流行的库)的创建者所维护的一个 Angular 2 UI 组件库;
  • Wijmo 5——一个商业的 Angular 2 UI 组件库,必须要购买开发者许可证才能使用它;
  • Polymer——Google 所提供的一个外观漂亮且可扩展的组件库。在我们公司中,我们使用 Polymer 组件创建过一个实验性的 Angular 2 应用,但是它们两者的集成还有提升的空间;
  • Material Design 2——Google 专门为 Angular 2 所开发的 UI 组件库。目前,这个库处于早期的 Alpha 版,但是开发状态非常活跃,在未来的三四个月中,我期待能够看到十几个设计良好的 UI 组件;
  • NG-Lightning——一个 Angular 2 的组件和指令库,它是使用 TypeScript 和 Lightning Design System CSS 框架完全从头编写的。

现在,使用 Angular 2 安全吗?

在 Farata Systems,我们从第一个 Beta 释放版本就将 Angular 2 到了实际的项目之中,在这个过程中并没有遇到严重的问题,至少没有遇到过找不到解决方法的问题。

如果你希望更安全的话,那么可以再等几个月。据说,Angular 2 的发布候选版本将会在 2016 年 5 月份的 Google I/O 会议上发布。(在 ng-conf 2016 召开前夕,Angular 2 已经发布了候选版本。——译者注)

将来会怎样呢?

在 2016 年 3 月 O’Reilly 举办的 Fluent 会议上,Brad Green 做了一个 keynote 演讲,可以观看该演讲的 视频。你被感染到了吗?反正我是被感染了。

关于作者

Yakov Fain住在纽约,他是一位 Java Champion,并且是 IT 咨询公司 Farata Systems 的合伙人。他领导着 Princeton JUG,写过很多关于软件开发的文章以及多本图书。最近,他与别人合著了《Angular 2 Development with TypeScript》,这本书将会在 2016 年 6 月由 Manning 出版。Yakov 经常在技术会议上演讲,并且参与讲授 Java 和 Angular 2。他的博客是yakovfain.com

查看英文原文:Angular 2 and TypeScript - A High Level Overview