引言
在一年多之前,我写了一篇文章《The way to Angular 2 | Part.1》介绍 Angular,最初打算持续完成一个系列,然后在我写完文章之后,Angular 和 TypeScript 都发生了相当大的变化,整个系列也搁浅了。 如今,我打算换一个角度,继续这个系列。
为什么要使用 Angular
为什么要使用 TypeScript
你愿意使用 Angular 吗?
1. 关于 Angular 这个名字
首先你需要理解并接受,Angular 特指 Angular 2及以后的版本,而以前所说的 Angular 1现在就是 angularjs。
2. 来看一下初始化一个 Angular App 的代码
// src/app/app.modules.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [ BrowserModule ],
providers: [ Logger ],
declarations: [ AppComponent ],
exports: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
// src/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
-
@angular 这个库(模块) 大而全的框架带来的第一个问题就是使用上复杂度的问题,在
@angular/core
这个模块下,有一堆你可能用得到,也可能用不到的模块,仅仅记忆这一些模块,可能就是一件容易让人放弃的事情。 -
根本不知道它想干嘛的方法
platformBrowserDynamic().bootstrapModule()
可能是你遇到的第一个不明所以的方法,从模块和方法名定义上,可能很好理解,大概就是在一个动态的浏览器平台上,启动一个模块,但是它到底干了什么,很遗憾,官方没有任何说明,只是告诉你,就这么启动应用就对了。某种角度来说,我确实不太关心它到底是怎么启动应用的,这是 Angular 自己的事情,但是如果我连这个应用的初始化逻辑都不了解,我怎么能确定,假如我在开发的时候遇到了问题,可以去解决?
当然你要问了,那么这个
platformBrowserDynamic().bootstrapModule()
到底干了什么? 现在 Angular 的启动说明文档也没有明确介绍,以前 Stack Overflow 上的回答也多半过期了,如果你想知道,大概只能去看源码了。platformBrowserDynamic().bootstrapModule()
这个问题只是现在 Angular 很多问题的其中一个表现,也许官方刻意想要弱化这些不需要开发者关心的问题,但我始终觉得这是一个令人感到不安的点。 -
在 RC 阶段,灵机一动加入的 NgModule 设计 从名义上来讲,RC 阶段改改设计,改改功能好像也在情理之中,但是 NgModule 这个改动(引入)显得动作幅度有点大,可能在早期每一个 Component 都需要在 metadata(decorator)里面声明一大堆依赖的做法被吐槽得太多,官方决定要简化一下操作,于是引入了 NgModule 这个设计。
NgModules help organize an application into cohesive blocks of functionality.
开发一个 Angular App 的时候,首先有的就是 Root AppModule, 这个模块会在初始化的时候直接被启动。官方把大部分基础组件也做成了 Module,比如
BrowserModule(CommonModule)
,FormsModule
。 然后根据官方的建议,你可以先设计核心模块,然后根据功能来划分模块,然后再设计一些公共模块,在每个模块里声明好 metadata:@NgModule({ providers?: Provider[] declarations?: Array<Type<any>|any[]> imports?: Array<Type<any>|ModuleWithProviders|any[]> exports?: Array<Type<any>|any[]> entryComponents?: Array<Type<any>|any[]> bootstrap?: Array<Type<any>|any[]> schemas?: Array<SchemaMetadata|any[]> id?: string })
看上去好像都是 optional 属性,但是实际上,但凡你定义一个 module,你基本都不能避开 providers,declarations,imports 和 exports,然后还需要去解决各种 conflict 和 re-export 的问题。你可能会想,为什么我要搞这么复杂呢?我在写 React,Vue 的时候哪有这么复杂的问题,开发起来轻松愉快。 我会先推荐你阅读一篇 NgModule 相关的文章,Understanding Angular modules (NgModule) and their scopes,不过我并不期望这能解决你的疑惑,并使你接受 NgModule 的设计从而认可它。 所以,接受并主动去深入 Angular 的设计模式,也可能会成为你通往 Angular 的路上的关卡,我看到社交媒体上不少的人,都是因为 Angular 的这些设计模式,而放弃了它。
3. Dependency injection
早在 angularjs 的时候,它的依赖设计就颇具争议,现在到了 Angular 也一样。依赖注入深入到了 Angular 的方方面面,是它实现模块化、作用域管理的核心之一。 那么又回到了上面的问题,你必须记住,要在合理的 Module 层和 Component 层注册 providers,也对你的整个应用规划提出了很高的要求,也许为了方便,你会选择在 Root AppModule 直接注册所有服务,来方便在所有子模块(甚至没有子模块,只有子组件)里注入服务。 大多数时候,你可能很难感受到它给你带来的好处,毕竟你在使用没有依赖注入设计的前端框架进行开发得时候,也很得心应手。 当你需要将很多原本一个函数,一个 JavaScript 模块就可以完成的工作,提升到服务的层面的时候,你也许就不会喜欢这种设计了。
4. 关于各种核心架构
rxjs,zone.js,SystemJS 你能接受它们吗? 在 Angualr 的 HTTP 请求库重写成 HttpClient 之后,rxjs 的 Observe 模式已经是主流,那么你有没有理解 Observe 模式的意义在哪里? Do Observables make sense for http? 看完上面这篇讨论,你可能会有一些收获。然而你依旧需要改变你原先的习惯和想法,去主动尝试这些新的模式。
你愿意使用 TypeScript 吗?
在最初的时候,我一直认为,就像官方宣传的那样:
Starts and ends with JavaScript
从 JavaScript 到 TypeScript 应该不是一件很困难的事情,并且最初我也是这么感觉的,但随着 TypeScript 的版本不断更新(本文撰写时,最新稳定版本是2.4),我编写的应用、代码也越来越复杂,我才突然发现,并不是这么一回事。
你会选择「真正」地使用 TypeScript 吗?
给「真正」加上引号,实际上,在我过去三年使用 TypeScript 的经历中,不管是我自己编写的代码,还是我接触的其他使用 TypeScript 开发的项目,有很多都并没有真正地使用 TypeScript,其中相当一部分,只是利用了 TypeScript 的编译器,将它作为 Babel 之类的 JavaScript 代码转换器的替代品。例如:
- 编写 ES2015+ 的代码,利用 TypeScript Compiler 编译到 ES5。
- 把原来的 JavaScript 文件改为 TypeScript,然后加上一些可有可无的 type 或者 interface 定义,大量使用 any 类型来简化类型定义。
阻碍我「真正」使用 TypeScript 的原因是什么?
我当然理解使用 TypeScript 的好处是什么,也知道它的各种特性和语法,那为什么我还是没能写出「真正」的 TypeScript 代码呢?
使用 TypeScript 成为一个包袱
如同我在上面提到的,很多情况下,TypeScript(的编译器)被用来替代 Babel 等工具,甚至仅仅是用来处理 ES2015+ 的代码,在这种情况下,其实你有更好的选择。 比如你面对的浏览器或者 Node 环境,其实很大程度上已经支持了 ES2015+,大量特性都可以不用转换直接使用,而一些没有被支持的特性,你可以选择性地安装对应的 Babel 插件来支持,这样,不管是从维护性,还是从最终代码性能的层面,都会更有优势,而不需要用 TypeScript 一股脑的转换到 ES5。
TypeScript 让我觉得无形增加了工作量
你可能立刻就要反驳我了,没关系,我认为每个人都会有自己的观点,而我也可以说说自己的处境。 大多数时候,我维护的是一些编写了一次之后,只剩下维护,并且没准下一次就会直接推到重来的代码。我可以给某一个数据模型定义对应的 interface,并且在所有它被使用的地方加好类型声明,但是很可能它参与的代码总量,还没有声明它的 interface 的代码来得多。很可能它因为各种各样的历史原因,会是两种,三种甚至完全不确定的类型,这时候,我想我会选择给它一个 any
。 我也并没有维护一个很大规模的应用(代码库),也没有超过两位同时维护同一份代码的同事,我的绝大多数代码都属于就地调用,声明的地方和调用的地方可能不超过一个屏幕。
TypeScript 引以为傲的工具支持并没有太大优势
这是我想要特别提到的一个点,不少人觉得,TypeScript 的工具支持(编辑器等),自动完成提示,类型解析,定义追溯等也是它非常大的一个优势,但是实际上,除去在 VS 家族(studio 和 code),像是在 WebStorm 等平台上,这些特性并不一定需要通过 TypeScript 来实现。利用 JSDoc 之类的语法,在 JavaScript 中,我们也可以实现相同的效果,可以定义属性类型,定义方法参数、返回值,由此,在编辑器中,我们一样可以实现上述效果。
为什么我还在坚持 Angular 和 TypeScript
这可能是你最关心的问题,既然这两个技术有上面那些让我觉得困惑、难受的地方,为何我还在主动使用它们?
优秀的技术终将有它的价值
- Angular 的架构设计,让我感受到了一个前端框架的魅力。
当你在使用 Angular 开发一个应用时候,你会主动去思考 Module、Services、Component 的规划,合理安排它们相互之间的依赖关系,主动去用更优美的架构实现你的应用,此处再次贴出这张官方架构图: ![](https://i.loli.net/2017/08/31/59a7b38faf46e.png) 而不是把你的所有逻辑都糅杂到一个组件里面,然后在另一个组件里面复制一遍。
- TypeScript 的价值
TypeScript 的编译器的价值很容易被人忽视,一个优秀设计的编译器,能给你非常大的帮助,不管是当你在编辑器中编写代码,还是使用 TSC 编译代码的时候,类型检查都可以避免非常多的问题。 我当然不希望我辛辛苦苦编写的代码,仅仅因为一个参数类型的错误而导致额外的麻烦,毕竟 JavaScript 中的隐式类型转换有便利,也有不利。 相比起写更少、更优雅的代码(这是 ES2015+ 给我们提供的)来说,编写更加安全可靠的代码显然价值更大。
Keeping Up with the Emerging Technology
我直接引用了 John Papa 的这句话,新兴的技术推动了 Web 开发的前进,我想现在大家都没有兴趣回到那个用 ES3、ES5 和 jQuery 去和跨浏览器兼容搏斗的年代了。 主动追求更加先进、优雅和完备的技术,应该是一件值得骄傲的事情。正如当初以 ember 为代表的一众框架,将 MVC/MVVM 架构引入了 Web 开发,从而改变了整个行业一样。同样还有 HTML5、CSS3、ES2015+ 的大量贡献者,真正推动了现代 Web 开发的发展。
These tools are the current answer to how we move forward without leaving behind our customers.
后续
相比起这些值得吐槽的缺点来说,Angular 和 TypeScript 还有更多值得我们去使用的优点。