Angular应用架构设计-4:响应式编程

这是有关Angular应用架构设计系列文章中的一篇,在这个系列当中,我会结合这近两年中对Angular、Ionic、甚至Vuejs等框架的使用经验,总结在应用设计和开发过程中遇到的问题、和总结的经验,来说一下Angular应用的架构设计相关的一些问题,包括像组件设计、组件之间的数据交互与通信、Ngrx Store的使用、Rxjs的使用与响应式编程思想。这些设计思想和方法,不仅适用于Angular,也适用于Vuejs、React等前端框架。当然,应用架构设计没有一个放之四海皆准的标准,他只能是根据具体情况具体分析。如果大家有更好的想法,欢迎交流。

  1. Angular应用架构设计-1:显示组件和功能组件
  2. Angular应用架构设计-2:Data Service模式
  3. Angular应用架构设计-3:Ngrx Store
  4. Angular应用架构设计-4:响应式编程
  5. Angular应用架构设计-5:设计原则

在之前的那篇 可订阅的Data Service中,我们多次提到,通过订阅的方式来获取修改的数据,其实这就是响应式编程风格的应用。

响应式编程是一种编程风格,它使用异步、事件驱动和观察者模式来处理问题。在传统的变成风格中,我调用一个方法,那个方式执行完成后,把结果返回给我,我也可以直接拿到返回的结果继续我的工作。在响应式编程风格当中,我把我要调用的请求作为一个事件发到某个指定的地方,自然会有人去处理,处理完后别人会调用我的某个回调方法,把处理结果发给我。如果大家对设计模式有了解,就应该看出,观察者模式实际上就是响应式编程风格的应用。

下图就很好的体现了在使用Ngrx实现单向数据流和事件流时,Observable的数据流的作用:
store-component-stream.png

用户触发的事件是写到一个流中,事件处理后的数据也写到一个流中,并通知调用者,我们又可以用Rxjs的switchMap来保证处理后的数据流的顺序,和输入的事件流的顺序一致(没有及时返回的数据结果会被抛弃),整个过程就好像一个自动化的生产线。

为了更好地理解响应式编程风格跟传统风格的区别,我们来看一个简单的处理用户输入的例子。很多用Angular框架的人,可能写出来的代码还是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component({
selector: 'some-component',
template: `<h2>Key Up Event Example</h2>
<input (keyup)="onKey($event)">
<p>{{val}}</p>
`
})
export class SomeComponent {
val;
onKey(event:KeyboardEvent) {
this.val = (event.target).value;
}
}

这还是传统的开发方式,也就是在一个输入组件上绑定一个事件,然后写一个这个事件的处理函数,处理完了,更新一些值,再更新到模板上。

这样的代码,可能在某些初级的介绍Angular的教程里会经常出现,但是,这种写法几乎违背了Angular这种现代开发框架的所有原则,如我们之前说的显示组件和功能组件的分离,展示模块和控制模块的隔离,单向的数据流和事件流,可订阅的数据流和响应式的事件处理。

对上面的代码进行重构,我们可以使用Rxjs的fromEvent(elelent, event)方法将这个input是数据流转换成一个Observable的事件流,也就是把用户输入的文字转成事件,每当用户输入或删除内容,就会产生新的事件;然后在一个Service类里去处理用户输入的信息;然后将处理后的结果,放到另一个Observable的对象里,并绑定到模板上。

但是,Angular给我提供了一种更好的处理用户输入的组件,也就是Angular的Forms模块,根据我们使用的方式,这个模块包含两个模块,FormsModule(模板驱动表单)和ReactiveFormsModule(模型驱动的表单)。有关这两种可以阅读我之前的文章:模板驱动的表单模型驱动的表单

我们使用模型驱动的表单,也就是响应式表单,处理上面的用户输入就更加方便。

1
2
3
4
5
const input$ = <FormGroup>this.userForm.controls['address'];
input$.valueChanges.debounceTime(1000).distinctUntilChanged().subscribe(cityValue => {
const msg = cityValue + ' 欢迎你!';
userService.welcome(msg);
});

使用响应式表单,不但能流式的处理用户输入,还能加延时,判断重复消息,总之结合ReactiveFormsModule和Rxjs,可以很方便的实现很多用户交互的功能。对Angular中如何使用Rxjs不了解的可以看看我这篇文章利用Angular2的Observables实现交互控制

这些年,响应式变成风格越来越得到认可,因为它有很多优点:

  1. 不会阻塞。A方法调用B方法,它的结果是异步通知A的,A不需要等在那边等B返回结果,可以继续做自己别的事情。在JS里,特别是NodeJS里,很多方法都是异步的,例如访问网络数据,访问本地文件,访问数据库等等。所以用NodeJS开发的应用,会有很多回调。当然,对于异步调用,如果不能很好地设计,很容易陷入回调的黑洞中。
  2. 不需要多线程。正是由于上面说的,响应式编程的所有方法都不会因为被调用的方法而陷入等待。我们一般说的后台执行任务,也就是在不影响当前方法继续执行的情况下,让某些方法在别的地方、或别的时候执行。在使用响应式编程实现的系统当中,我们一般只需要2个进程就能达到非常高的并发性,一个进程负责和用户交互、接受请求等,另一个进程就处理请求的事件(当然如果有多核也可以用更多的进程处理时间,增加吞吐量)。
  3. 不需要考虑多线程。在响应式编程中,事件是一个个生成并发送到一个类似队列的地方,而处理这些事件也是一条一条处理。特别是在js中,都是单线程处理,所以我们不需要考虑多线程的问题。即使在其他响应式开发的系统中,我们也可以通过制定一个策略,把事件分成几个组或类型,交由不同的处理器执行。例如事件按照用户id分组,保证同一个用户的事件只会被同一个线程的事件处理器处理。这样也能保证线程安全。
  4. 降低系统组件之间的耦合度。在使用响应式编程时,我们只需要定义给用户展示的数据结构,和用户产生的事件的数据结构,至于这个事件谁处理、如何处理都不需要关心。当业务改变导致数据结构和处理逻辑修改时,我们只需要定义好新的数据结构,然后在组件和Service里各自独立的去完成、测试。
  5. 可以很容易的扩展。在使用响应式编程开发的系统中,如果请求过多,一台机器处理不过来,我们就可以增加机器,只要这些机器能够同时访问事件流。我们再通过上面提到的事件的分组策略,实现分布式和高并发。

当然,响应式编程也有它的缺点,最大的问题就是对开发人员的思维的转变,而这往往也是最难的。例如上面的那个实例,我们只是根据用户输入简单的显示到页面,那我们也要用Observable对象,去生成事件,去响应事件,在有些时候,确实会很繁琐。但是,从长远考虑,要想让一个产品长期可维护,就必须从这样的小事做起,时刻要求自己使用良好的编码规范、设计思想,而不是图一时省事。

坚持原创技术分享,您的支持将鼓励我继续创作!