最近在重构手上的一个 Angular 项目,以前是用的本身写的一个仿 Elm 架构的库来进行的状态管理,期间遇到了这些痛点:typescript
其中,1、二两点是促使我重构的缘由,第三点是促使我更换状态管理方案的理由(太懒了,根本不想去维护这个项目)。npm
Elm 架构中,有下面几个重要的概念:api
这三个概念用 Elm 代码来实现显得很是优雅:数据结构
可是 JS 并无这些语言特性,大部分的行为须要靠大量额外的代码来模拟,并且模拟的效果也只是差强人意。架构
在处理异步操做的时候,咱们每每须要处理三种状态:请求中、成功、失败,这放在 Elm 中,每每须要三个 Message 才能进行完整的描述,并且还须要 3 个对应的 Update 分支,用 Elm 来写的话其实也还好,可是若是要用 JS 来实现的话,那可真是让人头大。框架
这是 Angular 生态中最强大的专门用来作状态管理的库了,流行度能够排上 No.1。各类各样的配套设施很是齐全,并且对 Redux 用户、RxJS 用户比较友好,不过这两个优势也就意味着它对新手不太友好,并且代码量与我如今的方案相比也很难减小。一样的,它所支持的全局单一状态让我不是特别感冒,若是能够的话,我更但愿可以使用模块单一状态。异步
这是一个简化版的 NgRx 库,相较于 NgRx 来讲,它要显得简洁不少,可是仍是免不了写那么多的样板代码(Action 之类的东西),并且一样也不支持我想要的模块单一状态。this
这是一个对框架无依赖的状态管理库,可是从样例代码风格上来看,更适合 OOP 风格的 Angular。它支持多个 Store,也支持在任意位置修改 Store(官方建议在一个专门的 Service 中修改 Store),这两点让它看起来更像是一个 OO 的 MobX。在最开始接触到这个库的时候,我已经很是心动了,基本上知足了个人大多数需求,不过在稍微进行了一些实践事后,仍是选择了放弃,由于太过 OO,因此样板代码又经过另外一种方式膨胀了起来。code
MobX 理念我很是喜欢,因此我本来是很想把 MobX 跟 rxjs 搭配使用的,可是 MobX 来到 Angular 应用中后显得有些水土不服,在不少地方都须要手动的将把 RxJS 与 MobX 进行来回的转换,这大大的增长了个人重构成本,最终只能做罢。rxjs
最终,我没有选择以上任何一种备选方案,而是选择使用 immutable 与 RxJS 来混搭一个状态管理方案—— ImmutableSubject
。
其源码以下所示:
import { BehaviorSubject } from 'rxjs'; export class ImmutableSubject<TImmutable> extends BehaviorSubject<TImmutable> { update(action: (value: TImmutable) => TImmutable) { this.next(action(this.value)); } }
我仅仅只是为 BehaviorSubject
作了一点点的拓展,让调用方更方便地修改 Store。使用起来就像下面这样:
import { Injectable } from '@angular/core'; import { ImmutableSubject } from './ImmutableSubject'; import { List } from 'immutable'; import { BlogCategoryEditDto } from '../models/BlogCategoryEditDto.model'; import { CategoriesService } from '../api/categories.service'; import { BlogCategoryType } from '../models/BlogCategoryType.enum'; @Injectable() export class CategoryStore { constructor( private cateSvc: CategoriesService ) { } /// ======= Queries ======= $categories = new ImmutableSubject(List<BlogCategoryEditDto>()); /// ======= Actions ======= refreshList(cateType: BlogCategoryType) { this.cateSvc.list(cateType).subscribe(categories => { this.$categories.update(x => { return List(categories); }); }); } addCategory(dto: BlogCategoryEditDto) { this.$categories.update(x => x.push(dto)); } deleteCategory(categoryId: number) { this.$categories.update(x => x.filterNot(c => c.categoryId === categoryId)); } updateCategory(dto: BlogCategoryEditDto) { this.$categories.update(x => x.map(c => c.categoryId === dto.categoryId ? dto : c)); } }
Store 类中使用 ImmutableSubject
做为真正存储数据的地方,同时使用两行注释将查询与修改进行分离开来,从形式上极大地减小了样板代码的出现。
对于异步操做来讲,能够直接让 Action 返回一个 Observable 来指示异步处理的过程,而 Query 自己就是 Observable ,因此能够天然而然的得到处理异步的能力,而这并不须要添加太多额外的代码。
Immutable 为咱们提供了大量使用的不可变数据结构,能够在 JS 中实现 Record Type 大部分的特性。
Immutable 与 RxJS 都是很是出名的第三方库,因此根本不须要费心去维护升级,并且它们的维护者也很是的可靠,在将来很长一段时间内它们也均可以得到及时的更新。
尽管个人 ImmutableSubject
看起来很是的简陋,但他确实能够在必定的约束下为我解决问题。在使用 ImmutableSubject
的时候须要遵照如下约定:
update
方法进行pipe
从 ImmutableSubject
中派生ImmutableSubject
须要手动释放使用相似 Elm 架构的一个好处就是可使用“时间旅行”功能,由于全部对状态的修改都会被记录下来,或者说,当前状态就是由历史操做记录积累而成(即事件溯源)。不过这个功能我并不须要,对于我如今的应用规模来讲,事件溯源不只不能直接解决任何问题,反而会提升代码的复杂性。对于我来讲,仅仅只是 CQRS 就已经足够了。