本文跟随着 Angular 的变迁聊聊这个框架,分享一些个人的理解。
本骚年刚好是在之前的项目中使用到 Angular1,然后现在项目中负责将 Angular-beta 升级到 Angular-release 版本。与 Angular 结识较深,或许也是缘分吧。
# 与 AngularJS 共事
# 初始
算了算,或许本骚年大概是两年前开始认识 AngularJS,那会 backbone.js、underscore.js 也还是刚开始流行,而热门的当然还是 jQuery、zepto 那些吧。 不得不说,当你去仔细理解 jQuery 的整个设计和架构的时候,才会知道里面的精华和美妙之处。而对 jQuery 的熟悉也使得我养成了从 dom 出发的编程思维。
而 AngularJS 的出现,则颠覆了大多数 jQueryer 的思维方式。是的,我们需要从数据和状态开始思考组件,而不是随意地调动 dom 做想做的事情了。
什么依赖注入(DI)
、控制器(Controller)
、指令(Directive)
、服务(Service)
,Scope
又是什么,为什么还有digest()
和apply()
。
很多完全没接触过听说过的概念一拥而入,我没写过 JAVA,工程化又是什么? 当时的我,并不知道这些 MVC/MVVM 框架将对前端产生怎样的一个影响,或者说是带来哥怎样的未来。
然而,就像我开始接触 javascript 一样,非科班的我没学过其他的语言,所以也不懂得为什么很多人诟病它。 AngularJS 的出现也是一样,从我和它们的初始开始它们就是以这样的形态存在,带来方便的同时也免不了有自己的小缺点,没有对错,没有好坏,只有优势或是不适合的一些场景。
# 并肩作战
在一遍又一遍去理解 AngularJS 中的各种新概念和思维,以及一些业余的尝试之后,我开始把它选为队友,一起战斗。
那时候其实 React 已经热门起来了,而 Vue 还没稳定。基于函数式编程的 React 受到很多人的追捧,而我因为还没去了解熟悉,光从社区和论坛的讨论中无法分辨好坏。
直到产品经理问我,为什么 AngularJS 不行呢? 是啊,为什么不行呢?于是乎我把项目从 jQuery 迁移到 AngularJS 上了,准确来说,重构了。
坑是不少,但是越到后面才越发现这次重构的正确性。项目的维护和拓展性能大幅度提升了,于是进入了业务模块快速开拓的阶段。
其实我一直认为,作为一个业务产品,除了性能上的瓶颈,基本上没有框架好坏之分,只有设计架构的人能否正确理解业务和进行抽象架构。
# 框架纷争
AngularJS 的工程化,一直到现在都会比 React/Vue 等框架更适合大规模业务的管理吧。尤其是像 PC 端的管理系统,围绕着简单的增删查改,需要的只是快速拓展业务,而很少复杂的需求和设计。 听过一句话,好的语言设计,能让刚接触的人写出的代码,和牛逼的人写出的代码几乎一样。
函数式编程也好,面向对象也好,其实方式并不是那么重要,我们的最终目标一致的话,其实剩下的更多是相互之间磨合、配合然后高效前进。 React 很棒,它的虚拟 DOM 提升了性能,函数式编程让我们不需要再针对对象设计,我们可以畅快地抽象成一块块功能,然后一层层地封装或者调用。状态这种本不应该维护在函数式编程里面的东西,就让 Redux/Flux 等抽离出来进行管理吧。 Vue 也很不错,它相比 React 对前端人员友好和易上手,性能也紧跟 React,相比 AngularJS 要轻量、性能要好。尤其随着移动端的应用场景越来越大,单薄的 H5 页面很是适合吧。
看看 AngularJS,概念太多上手难,框架太笨重不够灵活,甚至每次的升级都不做向下兼容(AngularJS、Angular-beta、Angular-release)。
然后我们对 Angular 的认识停留在这些一般般的风评中,忘记了它的工程化场景更适合大量业务模块的管理,也忽视了它的自我颠覆同时带来了一些很棒的特性(Rxjs
和模块化),也不曾注意到现在的它更轻量、更灵活了。
又或者是原 AngularJS 的使用者,看着 Angular 的断崖式升级以及 beta 时期也在不断跳级,望而止步,也没机会深入探视,然后发现灵魂还在,光芒耀眼。
其实框架很多很杂,无法分辨不敢恭维又怎样。每个曾处于浪尖的新事物,必然有它的过人之处,多去了解学习,然后把精髓和优秀的思维带走,把不足记下以防踩坑。 若不去认识认识,怎么会知道这些框架的相似(路由、HTTP、生命周期、模板引擎等)。同样是服务的设计,依赖注入新的服务实例,而使用状态的抽离管理或许需要自行重置状态。
# 打怪升级
ES6 和 Webpack 的出现,使得很多在 AngularJS 中的设计变得冗余或者稍微落后了。新式特性和语法糖,模块化在 Webpack 配合 Babel 的强助攻下破浪而出。 然后 Angular 推翻重来,出现了 Angular-beta 版本。当初宣言不进行 AngularJS 版本的兼容使得它吃了不少亏,毕竟后面还是出了迁移方案的。
我们的项目从 AngularJS 1.2 升级到 1.5+,然后随着浪潮甩掉 Grunt 和 Gulp,调整上了 Webpack,同时 AngularJS1.5+的版本已经部分引入了 Angular 的一些新的思维(模块化),以及可以尽情使用 ES6/ES7 的新特性了。 不得不说,像 Promise、Module、Class 这些 API 的出现,真的是改变了很多的写码方式和效率。
然后因为项目业务的不断拓展,以及维护人员的增加,团队到后面一致赞同 Typescript 的引入。事实证明约束的同时也省去了一些不必要的交流,代码的维护性变强了。
这个时候我们的项目状态是,AngularJS 1.5
+ Webpack
+ Typescript
,同时因为联调的排期问题我们使用了 Angular 的 mock,真的很实用。
# 你好 Angular
现在的项目也是相似的,PC 端的管理系统。当然它已经换了新马甲,虽然还是 beta 版本,不过很开心地说:你好呀,Angular。
控制器(Controller)变成了组件(Component),然后定义都变成了装饰器(@decoretor from es7/typescript)。工厂(factory)没了,服务也要声明可注入(@injectavle)。
然后依赖注入的方式变了,beta 版的每次使用分类注入(分 directives/providers/pipe 注入),然后 release 版本的模块注入(NgModule 的 decleration/imports/exports/providers)。
事件绑定((click)
)和属性绑定([ngClass]
)的方式变了,定义某类组件都使用了 Class,我们看到了 this,作用域 Scope 被藏了起来。
Angular 的 beta 到 release 版本也出现了一些大的变动,导致当初的版本已经找不到对应的文档和 API 了,遇到问题只能翻源码的效率太低了,于是我们升级到 release 的 4.0 版本。
Angular-beta 到 release 也有不少的调整吧,具体大家可以参考《从 Angular-beta 到 Angular4-release 框架升级总结》 (opens new window)。
项目里面也有些调整,上了 Angular-cli,它的内核也是 webpack。虽然不是很灵活,但在没有很多时间自己实践的情况下还是个不错的选择。然后有了 lazyload,有了公用组件模块 SharedModule。 团队的小伙伴也说过这么一句话,Angular 的项目,基本上拿过来复制粘贴就可以快速相应需求了。
我们的工作很多时候最终目标是一个很棒的产品,无论是体验还是对用户友好。当然免不了地,对自己有些要求的我们也想要一个看起来漂亮的代码和项目架构。 而我个人的习惯一般是,在产品刚启动的时候快速开发,然后规模大了,进行抽象、规范、架构调整,以及多人配合时如何提高开发效率,减少开发和维护成本。
很多人说,你要考虑以后的拓展性,要设计得完美。只是很多时候我们的产品总不能按照想象的顺利发展,我能做的,是和目前项目状况最适合的设计和架构,不多也不少。
# 谈谈 Angular
# 依赖注入
Angular 的依赖注入可谓是灵魂了,之前有篇详细讲这个的文章《谈谈 Angular 中的依赖注入》 (opens new window),这里大致摘录一些。
依赖注入在项目中,体现为项目提供了这样一个注入机制,有人负责提供服务,有人负责消耗服务,而这样的机制提供了中间的接口,并替使用者进行了创建并初始化这样的处理。 我们只需要知道,拿到的是完整可用的服务就好了,至于这个服务内部的实现,甚至是它又依赖了怎样的其他服务,都不需要关注。
其实像我们设计一个项目,自行封装的一些组件和服务,然后再对它们的新建和初始化等等,也经常需要用到依赖注入这种设计方式的。 我们的服务也可以分为有记忆的和无记忆的,关键在于抽象完的组件是否内部记录自身状态,以及怎样维护这个状态等等,甚至设计不合理的话,这样的状态管理会经常使我们感到困扰,所以 Redux、Flux 和 Mobx 这样的状态管理框架也就出现了。
而 Angular 在某种程度上替我们做了这样的工作,并提供我们使用。 在 Angular 里面我们常常通过服务来共享一些状态的,而这些管理状态和数据的服务,便是通过依赖注入的方式进行处理的。 依赖注入还有有个很棒的地方,就是单元测试很方便,测试的时候也注入需要的服务就好了。
# 模块化组织
Angular 模块把组件、指令和管道打包成内聚的功能块,每个模块聚焦于一个特性区域、业务领域、工作流或通用工具。 我们不再只是将组件和功能进行模块抽象,也不只是再将一些组件和功能进行再封装,我们还要把这样的功能模块一步步放射到整个应用程序。
这样的模块化思想层层包裹,我们的结构组织也层层地抽象封装,树结构的设计思想从模块组织到依赖注入延伸。 同时,依赖注入使得每个模块,不管层次薄的厚的,都能方便简单地进行状态管理。
Angular 也提供了封装好的一些功能模块,像表单模块FormModule
、路由模块RouterModule
、Http 模块等等。
# 数据绑定
我们都知道,在 Angular 中是使用双向绑定的,当然 Angular 中调整了分为事件绑定和属性绑定。
这在很多时候都解决了不少的问题,增删查过或者父子通讯最爱用了,换成 React/Vue 还得手动setState()
之类的更新或者通过状态管理工具触发更新。
说脏检查性能太糟,在 Angular1 中,当数据状态多了一次变更可能导致多次的联动脏检查计算,想想也是挺累的。 脏检查无非是,保存旧的数据,然后和新的数据比较计算,接着更新后再计算,直到没有新的变化为止。
虽然有了很多像 React 的虚拟 DOM,以及 Vue 的数据跟踪 getter 和 setter,但在 Angular 中依然使用脏检查。 不一样的是,添加了 zone.js 对异步任务进行跟踪,把这个计算放进 worker,完了更新回主线程,是个类似多线程的设计,也提升了性能。
在 Angular 中应用的组织类似 DOM,也是树结构的,脏检查会从根组件开始,自上而下对树上的所有子组件进行检查。 相比 Angular1 中的带有环的结构,这样的单向数据流效率更高,而且容易预测。
# 路由和 lazyload
路由在 Angular-release 版本中设计成单独的功能模块,不再跟组件 Component 有密切的关系。
我没有仔细研究过其他框架的路由模块,不过像 lazyload 这样的设计也肯定已经考虑进去了吧。 既然这样的话,那这里我拿 Angular 来说明一下 lazyload 的闪光之处。
像我们打包页面,很多时候最终生成了一个bundle.js
文件。这样,每次当我们请求页面的时候,都请求整个bundle.js
并加载,有了 Webpack 或许我们只需要加载其中的某些模块,但还是需要请求到所有的代码。
很多时候我们或许不需要进入所有的模块,这个时候浪费了很多的资源,同时首屏体验也受到了影响。
通过路由的 lazyload 以及上面提到的模块化,我们可以把每个 lazyload 的模块单独打包成一个chunk
文件,当进入模块时才请求和加载,当我们的业务规模很大的时候,首屏速度得到大幅度提升。
使用 Angular-cli 可以很方便地进行路由的 lazyload,而若自行使用 webpack 构建,也只需要加上一个angular-router-loader
就好了。
# 编程方式
Angular 相对 Angular1 来说,编程方式改变太多。但是如果你熟悉 ES6/ES7,就发现 Angular 中的很多编程方式都很熟悉。 其实 Angular 看起来和 Angular1 完全是两个框架,很多时候是编程思维习惯导致的。就像当初使用 jQuery 的我们去学习 Angular 的感觉,但其实像 ES6/ES7 的出现这是一个必然的趋势吧。
Typescript 的优势也体现在项目的维护性、代码的可读性以及多人配合的沟通效率。它也只是一种方案选择,当然如果有约定好的规范也是很不错的,最终还是看团队的意愿了。 Promise 的设计也让代码变得清晰连贯,数据状态的响应处理,同时我们可以方便地进行流式处理和异常处理。 Rxjs 的引入可以方便实现事件订阅,有时候可以配合 Websocket 来进行应用的部分推送更新等功能。 Class 的定义方式,配合装饰器 Decoretor,可以方便地统一的组件/服务/模块等的创建。
# 用 AOT 进行编译
JIT 编译导致运行期间的性能损耗。由于需要在浏览器中执行这个编译过程,视图需要花更长时间才能渲染出来。 由于应用包含了 Angular 编译器以及大量实际上并不需要的库代码,所以文件体积也会更大。更大的应用需要更长的时间进行传输,加载也更慢。 预编译(AOT)会在构建时编译,这样可以在早期截获模板错误,提高应用性能。
事实上只有一个 Angular 编译器,AOT 和 JIT 之间的差别仅仅在于编译的时机和所用的工具。 使用 AOT,编译器仅仅使用一组库在构建期间运行一次;使用 JIT,编译器在每个用户的每次运行期间都要用不同的库运行一次。
AOT 使得页面渲染更快,无需等待应用首次编译,以及减少体积,提早检测模板错误等等。
# 拥抱变化,迎接未来
身为框架,Angular 和 React、Vue 各有各的优劣,哪个更适合则跟产品设计、应用场景以及团队等各种因素密切相关,没有谁是最好的,只有当前最适合的一个。 Angular 的经常性不兼容,以及 1 到 2 的断崖式升级,或许对开发者不大友好。但其实,我很喜欢它的这些改变,干脆利落,自身的缺点由自己去克服。
我们也何尝不是,为何要死守某个框架、某种语言,或是争好坏、分高下。 何尝不抱着开放的心态,拥抱变化,然后迎接未来呢?
# 参考
- 《Angular 的变革》 (opens new window)
- 《Angular 脏检查过程》 (opens new window)
- 《不要把 Rx 用成 Promise》 (opens new window)
- 《预 (AoT) 编译器》 (opens new window)
# 结束语
以上只是本人的个人理解,或许存在偏差。世上本就没有十全十美的事物,大家都在努力地相互宽容和理解。
那些我们想要分享的东西,肯定是存在很棒的亮点。而我们要做的,是尽力把自己看到的那完美的一面呈现给大家。