网络上关于前端框架对比、选择的文章如恒河沙数,因此,很长时间以来,不愿,也难以下笔做些总结。时至近日,在写下数万行代码之后,在网站渐趋成熟之际,觉得可以“理直气壮”地输出一些想法、文字了,故在此从不同的角度做个总结,权且作为抛砖引玉。

绝大多数分析、对比文章都是从MVC/MVVM架构、功能、性能、学习成本/学习曲线等方面入手,比较Angular、React、Vue三大框架的优劣,然而,三大框架发展至今,无论是MVC/MVVM本身,还是功能、性能、文档、生态等方面,都已大同小异、相差无几,它们之间真正的差别并没有被充分发掘出来。本文从一些抽象的形而上的角度,谈谈为什么选择Angular。

1、软件工程

俗话说,“没有对比,就没有伤害”,要对比不同的框架,除去横向的几个前端框架之间,不妨再同后端的一些成熟的框架进行对比,比如Java的SpringMVC,PHP的CakePHP、Symfony,NodeJS的Express、Nest.js等。可以发现,Angular、Nest.js和SpringMVC就像孪生兄弟,思想、理念,甚至使用方式都如出一辙。

从Angular、Nest.js、SpringMVC框架上,可以清晰的看到各种设计模式、软件工程思想的应用,无论是拦截器、守卫、管道、装饰器、过滤器、中间件等抽象概念,还是代码的组织方式,都无时无处不令人感受到写代码时的那种愉悦的享受。

工业时代诞生了“流水线”,在软件工程领域,同样讲究分工合作,各模块各司其职,又通过某种方式彼此通信、互相合作,共同完成整个软件开发的生命周期。从这个角度,上述三个框架无疑是优雅的,它们没有各种杂糅,能清晰地看到各自的职责和边界。

以管道为例,作为数据处理、转换的典型方式,在Angular中应用广泛,也是官方推荐的数据表现层实现方案。比如,要实现日期数据的格式化展示,在没有管道的情形下,需要在controller层或service层或store层进行格式转换,而在转换前还需要再抽象出一个公共的工具方法实现日期格式的转换操作。然而,有了管道之后,无论controller、service,还是store,都无需任何处理,直接在view层按需引入相应的管道即可,简单、明了、高效。

xmlCopy code
  • 1
<div class="date">{{post.postModified | date:'yyyy-MM-dd HH:mm:ss'}}</div>
{{post.postModified | date:'yyyy-MM-dd HH:mm:ss'}}

实际上,这便是“管道”的本意:数据从管道的一侧“流入”,经过管道的处理后,从管道的另一侧“流出”,就像自来水通过管道流入热水器加热后再通过管道流出,实现水的冷热转换一样。

这是来自现实生活的智慧。

2、观察者模式(事件机制)

可以说,这是Angular的核心,甚至是JavaScript的核心,其应用在Angular中无处不在,无论是Angular的生命周期模型、MVVM模式,还是RxJS机制、组件间通信机制等等,都广泛使用了观察者模式。

实际上,这也是来自大自然的智慧。最原始的捕猎(逃生)行为,便是此模式的典型场景:等候猎物,猎物出现在视野中时,眼睛、耳朵、鼻子等感官发现异常信息,发送信号给大脑,开始进入捕猎模式,追踪猎物行踪,直至猎物到手,完成一次捕猎行为。在这一场景中,“猎物出现”就是一次事件,而捕猎者就是事件“观察者”。

在Angular中,路由参数变化、service调用、组件间通信等等,全都被设计成观察者模式,通过Observable实现事件订阅。而实际上,结合JavaScript天然的事件模型和异步机制,可轻松实现松耦合的异步编程。这一特点对于组件化编程而言,仿佛Angular天生是为此而生的。

3、面向对象和函数式编程

二者的优劣无需多言,在Angular身上,可以看到二者完美的结合,既能享受到接口、类、继承、私有属性等传统面向对象编程的好处,又继承了JavaScript天然的函数式编程特点。

4、RxJS

不得不说,RxJS的引入为Angular加分不少。以下面代码为例:

typescriptCopy code
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
this.route.queryParamMap .pipe( combineLatestWith(this.optionService.options$), skipWhile(([params, options]) => isEmpty(options)), tap(([params, options]) => { this.options = options; …… }) ) .subscribe(() => { this.login(); });
this.route.queryParamMap .pipe( combineLatestWith(this.optionService.options$), skipWhile(([params, options]) => isEmpty(options)), tap(([params, options]) => { this.options = options; …… }) ) .subscribe(() => { this.login(); });

如果不使用RxJS,难以想象要写多少行代码才能实现类似的需求,而即便实现了,其代码的可读性可以想象实在不敢恭维(想想一堆的if...else...语句)。

5、为未来考虑

在大中型软件设计、开发中,其扩展性、健壮性、可维护性不得不加以考虑,甚至是举足轻重、优先考虑的事项。

在以往的React(包括Vue)实践中,往往项目到了一定阶段,都会显得臃肿而难以维护,单个文件动辄上千行代码,甚至单个方法、函数也多达数百行,对于维护人员、新进入项目人员,无疑是令人头秃的。

固然,这里面有架构设计的因素,也有项目组成员能力的因素,但同样的人员配置,使用Angular却鲜少遇到类似问题。

就像强、弱类型之争,“没有规矩,不成方圆”,弱类型给予开发人员自由,但强类型却带来代码的健壮性、可读性、可维护性等等诸多好处。同样,Angular内在的一些设计思想、理念,无形中规避、化解了代码膨胀后的种种问题。

6、总结

一句话概括,便是:Angular的“用户体验”让开发人员感受不到代码的存在,而仿佛在完成一件件艺术品。