(code review记录 )用rxjs重构一个小功能

今天看同事帮我修改的代码。一段很简单的UI点击事件被修改成了基于的rxjs的实现。思路大概是这样的。

本来footer component 有一个triggerToggleNavMenu(),大概是这个样子。

....
triggerToggleNavMenu(){
    // 我原来的代码在这里直接操作DOM修改UI
}
...

同事把它改为发射一个event

triggerToggleNavMenu(){
    this.toggleNavMenu.emit();
}

当然他在component里先添加了一个事件发射器(EventEmiter)属性

@Output() toggleNavMenu =  new EventEmitter<void>();

这样把我的“简单粗暴”的点击修改UI的事件改造为了报告一个“事件”。而且事件发射器具有@Output() 修饰,说明是向父组件(page)报告。(参考: 1

那么父组件page如何获取这个事件呢? 可以观察父组件page的模板,在嵌入该组件footer的时候,有个属性绑定:

<footer .... (toggleNavMenu)="toggleFooterNavMenu()" >

这样,子组件footer中的toggleNavMenu就和父组件page里的方法toggleFooterNavMenu()绑定了。

在父组件里果然存在一个叫做toggleFooterNavMenu的方法,该方法就是调用了ui的toggleFooterNavMenu()方法。

toggleFooterNavMenu(){
    this.ui.toggleFooterNavMenu();
  }

这个ui是我们这个项目里为实现rxjs方便而组织的一个feature层的一个Facade(外框)。这Feature层相当于连通了状态机state 和 app 的一个中间层。在这一层包含了rxjs的所有实现的要素,比如action, reduce, selector

这个UI的Facade里,对整个事件处理就一句话:让store触发这个action的事件。

toggleFooterNavMenu() {
    this.store$.dispatch(UiActions.footerNavMenuToggle());
 }

这个store$就是大名鼎鼎的ngrx库里的store(状态)了。这个UiActions.footerNavMenuToggle()则返回一个通过createAction创建的一个action(我的理解action是一种自定义事件)。

触发这个事件会有什么“后果”呢?就是reduce了任务了,reduce负责“无副作用”地修改store里的状态。在reduce的定义中,我们已经定义好了这样的任务:

.......
on(action名字 (state) => ({
...state,
footerNavMenuOpen: !state.footerNavMenuOpen
})),

关键就一行 把footerNavMenuOpen 取反。因为我们这就一个bool问题,比较简单.

那么到这里为止,饶了半天这个点击动作仅仅修改了内存(store)中的一个state状态值?是的。但是一旦这个状态state发生改变后,会有一系列的“后续事件”。这就是rxjs的精妙所在。所有订阅了这个state的订阅者都会收到通知。

当然,这个状态也是事先定义好的。它有对应的状态模型。这里是指和ui相关的几个需要观察的对象的状态,比如某个菜单的开闭状态,初始状态等等。

在Facade 的中有一个可观察对象的属性:
 footerNavMenuOpen$: Observable<boolean>;
具体定义是通过一个选择器selector,选择订阅了这个状态。 
footerNavMenuOpen$ = this.store$.select(selectUiFooterNavMenuOpen);

这里的参数selectUiFooterNavMenuOpen是通过createSelecto返回的对state中,ui状态中某个特定属性名称。

当这个facade被注入到父组件page中,通过模板绑定时候

<footer [navMenuIsOpen]="ui.footerNavMenuOpen$ | async" , .... ></footer>

它又重新流回到了footer 组件里。

  @Input() navMenuIsOpen = false;

在footer 的模板里,我们又可以愉快地使用这个简单的变量来决定是否显示某些部分了。

 <div class="panel" *ngIf="!navMenuIsOpen">
    <!-- only for menu is open -->
  </div>

总结。在这个任务里,我们实现了一种”数据流“的闭环。即用户触发某个事件,该事件改变store中的某个状态,rxjs通知订阅该状态的”订阅者“,订阅者收到通知后完成自己的更新,比如更改dom. ,然后等待下个事件的触发。

这就是响应式编程 Reactive Programming。里面提到的Oberservble,就是streaming,是一种数据流,习惯上我们在命名变量的时候,需要在”流变量“名字后面加上一个$,来提示程序员,这是一个”流“。