最近在重構手上的一個 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,因此樣板代碼又經過另外一種方式膨脹了起來。spa
MobX 理念我很是喜歡,因此我本來是很想把 MobX 跟 rxjs 搭配使用的,可是 MobX 來到 Angular 應用中後顯得有些水土不服,在不少地方都須要手動的將把 RxJS 與 MobX 進行來回的轉換,這大大的增長了個人重構成本,最終只能做罷。code
最終,我沒有選擇以上任何一種備選方案,而是選擇使用 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 就已經足夠了。