zoukankan      html  css  js  c++  java
  • 使用 Immutable Subject 来驱动 Angular 应用

    现状

    最近在重构手上的一个 Angular 项目,之前是用的自己写的一个仿 Elm 架构的库来进行的状态管理,期间遇到了这些痛点:

    1. 样板代码太多
    2. 异步处理太过繁琐
    3. 需要单独维护一个 npm 包

    其中,一、二两点是促使我重构的原因,第三点是促使我更换状态管理方案的理由(太懒了,根本不想去维护这个项目)。

    样板代码太多

    Elm 架构中,有下面几个重要的概念:

    1. Message(在 Redux 中被称作 Action)
    2. Update(在 Redux 中被称作 Reducer)
    3. Model

    这三个概念用 Elm 代码来实现显得非常优雅:

    1. Message:可区分联合(Discriminated Union),还可以在单独的 Case 中使用元祖来内联复杂数据结构
    2. Update:对可区分联合使用模式匹配
    3. Model:不可变记录类型(Record Type),方便基于当前的值生成新的值

    但是 JS 并没有这些语言特性,大部分的行为需要靠大量额外的代码来模拟,而且模拟的效果也只是差强人意。

    异步处理太过繁琐

    在处理异步操作的时候,我们往往需要处理三种状态:请求中、成功、失败,这放在 Elm 中,往往需要三个 Message 才能进行完整的描述,而且还需要 3 个对应的 Update 分支,用 Elm 来写的话其实也还好,但是如果要用 JS 来实现的话,那可真是让人头大。

    备选方案

    NgRx

    这是 Angular 生态中最强大的专门用来做状态管理的库了,流行度可以排上 No.1。各种各样的配套设施非常齐全,而且对 Redux 用户、RxJS 用户比较友好,不过这两个优点也就意味着它对新手不太友好,而且代码量与我现在的方案相比也很难减少。同样的,它所支持的全局单一状态让我不是特别感冒,如果可以的话,我更希望能够使用模块单一状态。

    NgXs

    这是一个简化版的 NgRx 库,相较于 NgRx 来说,它要显得简洁很多,但是还是免不了写那么多的样板代码(Action 之类的东西),而且同样也不支持我想要的模块单一状态。

    akita

    这是一个对框架无依赖的状态管理库,但是从样例代码风格上来看,更适合 OOP 风格的 Angular。它支持多个 Store,也支持在任意位置修改 Store(官方建议在一个专门的 Service 中修改 Store),这两点让它看起来更像是一个 OO 的 MobX。在最开始接触到这个库的时候,我已经非常心动了,基本上满足了我的大多数需求,不过在稍微进行了一些实践过后,还是选择了放弃,因为太过 OO,所以样板代码又通过另一种方式膨胀了起来。

    MobX

    MobX 理念我非常喜欢,所以我原本是很想把 MobX 跟 rxjs 搭配使用的,但是 MobX 来到 Angular 应用中后显得有些水土不服,在很多地方都需要手动的将把 RxJS 与 MobX 进行来回的转换,这大大的增加了我的重构成本,最终只能作罢。

    最终方案

    最终,我没有选择以上任何一种备选方案,而是选择使用 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 的时候需要遵守以下约定:

    1. Store 中的状态应该是 immutable
    2. 对状态的修改只能在 Store 类的 Actions 中使用 update 方法进行
    3. Query 应该总是使用 pipeImmutableSubject 中派生
    4. 不再使用的 ImmutableSubject 需要手动释放

    关于 EventSourcing

    使用类似 Elm 架构的一个好处就是可以使用“时间旅行”功能,因为所有对状态的修改都会被记录下来,或者说,当前状态就是由历史操作记录积累而成(即事件溯源)。不过这个功能我并不需要,对于我现在的应用规模来说,事件溯源不仅不能直接解决任何问题,反而会提高代码的复杂性。对于我来说,仅仅只是 CQRS 就已经足够了。

  • 相关阅读:
    关于桌面的图片打开很慢的解决方法
    用c#进行递归组合
    ajax,js,css 入门页面
    择日宣判此案,却常再无下文
    共享软件的明确定义
    [转]大逃亡,还没出来呢
    如何收集带有附件的网页
    送一份自家产的软件给园内的兄弟姐妹作“福利”
    [转]评蒙牛内幕
    蓝侠==la*uan,破解中国共享软件联盟著名灌水专家“蓝侠””
  • 原文地址:https://www.cnblogs.com/JacZhu/p/10271143.html
Copyright © 2011-2022 走看看