使用 Immutable Subject 来驱动 Angular 应用
现状
最近在重构手上的一个 Angular 项目,之前是用的自己写的一个仿 Elm 架构的库来进行的状态管理,期间遇到了这些痛点:
- 样板代码太多
- 异步处理太过繁琐
- 需要单独维护一个 npm 包
其中,一、二两点是促使我重构的原因,第三点是促使我更换状态管理方案的理由(太懒了,根本不想去维护这个项目)。
样板代码太多
Elm 架构中,有下面几个重要的概念:
- Message(在 Redux 中被称作 Action)
- Update(在 Redux 中被称作 Reducer)
- Model
这三个概念用 Elm 代码来实现显得非常优雅:
- Message:可区分联合(Discriminated Union),还可以在单独的 Case 中使用元祖来内联复杂数据结构
- Update:对可区分联合使用模式匹配
- 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 的时候需要遵守以下约定:
- Store 中的状态应该是 immutable
- 对状态的修改只能在 Store 类的 Actions 中使用
update方法进行 - Query 应该总是使用
pipe从ImmutableSubject中派生 - 不再使用的
ImmutableSubject需要手动释放
关于 EventSourcing
使用类似 Elm 架构的一个好处就是可以使用“时间旅行”功能,因为所有对状态的修改都会被记录下来,或者说,当前状态就是由历史操作记录积累而成(即事件溯源)。不过这个功能我并不需要,对于我现在的应用规模来说,事件溯源不仅不能直接解决任何问题,反而会提高代码的复杂性。对于我来说,仅仅只是 CQRS 就已经足够了。
使用 Immutable Subject 来驱动 Angular 应用的更多相关文章
- RxJS之Subject主题 ( Angular环境 )
一 Subject主题 Subject是Observable的子类.- Subject是多播的,允许将值多播给多个观察者.普通的 Observable 是单播的. 在 Subject 的内部,subs ...
- [Angular 2] Refactoring mutations to enforce immutable data in Angular 2
When a Todo property updates, you still must create a new Array of Todos and assign a new reference. ...
- [Immutable + AngularJS] Use Immutable .List() for Angular array
const stores = Immutable.List([ { name: 'Store42', position: { latitude: 61.45, longitude: 23.11, }, ...
- Angular 4+ 修仙之路
Angular 4.x 快速入门 Angular 4 快速入门 涉及 Angular 简介.环境搭建.插件表达式.自定义组件.表单模块.Http 模块等 Angular 4 基础教程 涉及 Angul ...
- [转]Angular: Hide Navbar Menu from Login page
本文转自:https://loiane.com/2017/08/angular-hide-navbar-login-page/ In this article we will learn two ap ...
- CHANGE DETECTION IN ANGULAR 2
In this article I will talk in depth about the Angular 2 change detection system. HIGH-LEVEL OVERVIE ...
- Angular 4 变更检测机制 ChangeDetectorRef 使用方法
1.在angular 2中,回调函数的返回结果,不会自动更新视图层的显示,可以用 ChangeDetectorRef 来驱动angular更新视图. import {ChangeDetectorRef ...
- ionic2+Angular 依赖注入之Subject ——使用Subject来实现组件之间的通信
在Angular+ionic2 开发过程中,我们不难发现,页面之间跳转之后返回时是不会刷新数据的. 场景一:当前页面需要登录之后才能获取数据--去登录,登录成功之后返回--页面需要手动刷新才能获取到数 ...
- 使用OnPush和immutable.js来提升angular的性能
angular里面变化检测是非常频繁的发生的,如果你像下面这样写代码 <div> {{hello()}} </div> 则每次变化检测都会执行hello函数,如果hello函数 ...
随机推荐
- visual studio 2013怎样快速查看代码函数关系--代码图
可以发现没有调试运行代码时是无法查看代码图的,可以在某行加一个断点,如下图,并开始debug调试: 这时,就会在代码调试工具栏看到代码图按钮,点击它: 右边就会出现代码图了: 这下就方便多了. 不仅适 ...
- Note of Python Turtle
Note of Python Turtle Turtle 库函数是 Python语言中一个流行的绘图函数库.Turtle 意思是海龟,在Python中显示为一个小箭头,通过它的移动而留 ...
- 在windows 下使用eclipse进行编译和烧写
eclipse IDE是一款开源的前端编程软件,它提供了编写,编译和调试ESP-IDF项目的图形集成开发环境. 首先在https://www.obeo.fr/en/eclipse-download?I ...
- 《mysql必知必会》学习_第20章_20180809_欢
第20章:更新和删除数据 P140 update customers set_emails='elmer@fudd.com' where cust_id=10005; 更新多个列,用逗号隔开.注意被指 ...
- requests应用
一.简介 什么是requests模块: requests模块是python中原生的基于网络请求的模块,其主要作用是用来模拟浏览器发起请求.功能强大,用法简洁高效.在爬虫领域中占据着半壁江山的地位. 为 ...
- js生成随机固定长度字符串的简便方法
概述 碰到一个需求:用js生成固定长度的字符串.在网上查了很多资料,网上的方法都比较麻烦.我自己灵光一现,实现了一个比较简单的方法.记录下来,供以后开发时参考,相信对其他人也有用. js生成随机字符串 ...
- H5的Page Visibility API
概述 哈哈,又学了一个H5的API.今天突然对动态获取网页的选中状态很感兴趣,然后去查了下,发现真的有个API控制它--Page Visibility API.于是把学到的东西记录下来,供以后开发时参 ...
- Appium发送中文或其他语言的问题
1. 需要在配置信息中增加'unicodeKeyboard' = “True”字段,如下: def driver_weixin(platformVersion="6.0.1",de ...
- C#基础-gc算法
众所周知,c++是需要程序员手动管理内存的,然而手动释放内存很容易被程序员遗漏,从而导致资源浪费或内存泄露.为解决这个问题,垃圾回收器诞生了,代替程序员自动管理内存的释放.至于gc算法则是垃圾回收器清 ...
- 【不遮遮掩掩】Github上传本地代码以及常见问题解决方案
2019.20.18更新: 把完整命令打一遍吧,不然看完太累了不是 //初始化文件夹 git init //初始化目录,把基本文件下载下来,如ignore文件 git pull --rebase or ...