且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

JS中反应式数据存储方案对比 Edata与RxJS, Mobx

更新时间:2022-09-23 10:01:19

Javascript中的状态管理与监测其实一直都在正解决的路上,语言标准方面,从已废弃的Object.observe API,到进入到ES6标准的Proxy,但都只针对单一对象状态监听,对多层对象实现监听只能借助于外部库,于是产生了我们常见的一些解决方案,如RxJS,提出了新的概念 Reactive Stream,即可被观测的事件流(observable event streams);另一个流行库Mobx则遵循Observer Pattern实现,另外还有一个叫Redux,基于CQRS实现。别说去应用,光是理解这些概念,开发者们都要先掉几根头发。

其实上面几种方案都是围绕着可被观测的对象(Observable Object)这一主题,除了上面几个流行库,在此强烈推荐一个新的更好的解决方案:edata

以下我们用代码来说明对比这几个状态管理库的核心用法:

RxJS observable

export class DataStore {
  public count: BehaviorSubject<number> = new BehaviorSubject(0);
  update(newValue: number) { this.count.next(newValue); }
}

export class ButtonComponent {
  public count: number;
  constructor(private dataStore: DataStore) {
    dataStore.count.subscribe(x => this.count = x);
  }
  onBtnClick() { this.dataStore.update(this.count + 1); }
}

上面的代码,我们在 DataStore 设定了一个可供订阅的 count,初始值为0。ButtonComponentconstructor 添加了一个对 count 的订阅(subscribe),并将变动会存到 this.count 里。按钮点击后触发 dataStore.update() 动作,让数据源中 count 加一,进而触发ButtonComponentthis.count 改变。

Mobx observable

export class DataStore {
  @observable public count: number;
  @action
  update(newValue: number) { this.count = newValue; }
}
export class ButtonComponent {
  public count: number;
  constructor(private dataStore: DataStore) {
    observe(dataStore, 'count', x => this.count = x.newValue);
  }
  onBtnClick() { this.dataStore.update(this.count + 1); }
}

Mobx使用 @observable 装饰器使对象可被观测,以及 @action 装饰器来定义反应式方法,非常直观。不过这里注意一下 observe() 这段代码,有两个坑:1)dataStore 必须为对象,若是 primitive value 必须要包在 observable.box() 中,增加了复杂度;2)count 值必须要变化才会触发,若改变的是同值,则不会触发!

为了演示 Mobx 这个问题,我们设定一个情景,我们加入 isDataLoading 变量表明是否数据是否已加载。

export class DataStore {
  @observable public data:number;
  @action
  update(newValue: number) { this.data = newValue; }
}
export class ButtonComponent {
  public data: number;
  public isDataLoading: boolean;
  constructor(private dataStore: DataStore) {
    observe(dataStore, 'data', x => {
      this.data = x.newValue;
      this.isDataLoading = false;
    });
  }
  onBtnClick() {
    this.isDataLoading = true;
    this.dataStore.update(this.data);
  }
}

这里会产生一个BUG,isDataLoading 一直为 true,因为 this.dataStore.update(this.data) 这句没有改变数值,导致 observe 并没有触发。解决方案就是使用 data = observable.box(),然后使用 data.set(value) 来进行数值设置。

另外还有一个坑,在 Mobx v4 中数组类型判断要非常注意:

var numbers = observable([1,2,3]);
Array.isArray(numbers)  // false ???
Array.isArray(numbers.slice())  // true !!!

简单对比 RxJSMobx,可以看出 RxJS 是比较经典的 pub/sub 模式实现,Mobx 更贴近于声明式 + callback风格,但也有不少坑来自于它本身的实现。

重点来了,

快速上手,不想了解太多概念,坑又少,扩展性好,推荐使用edata

我们来看下同样的例子,使用 edata 如何实现:

import {edataProxy} from 'edata'

export const dataStore = edataProxy({
    data: 0,
    update (newValue: number) {
        this.data = newValue
    }
})

export class ButtonComponent {
  public data: number;
  public isDataLoading: boolean;
  constructor(private dataStore: EdataProxy) {
    dataStore.__watch__('data', x => {
      this.data = x.data.value;
      this.isDataLoading = false;
    })
  }
  onBtnClick() {
    this.isDataLoading = true;
    this.dataStore.update(this.dataStore.data);
  }
}

上面的代码来类比 Mobx,可以看到并没有用到非标准的装饰器语法,更贴近原生,并且没有 Mobx 的那些坑。

这里要注意edataProxy()方法,它使得 dataStore 可以直接使用JS原生方法来存/取对象,但依赖于ES6 Proxy,若要支持IE浏览器,则尽量使用 edata的低阶API

更多edata介绍可以参考另一篇文章:JS中反应式数据存储方案对比 RxJS, Mobx 与 edata

这个库的作者当然也是本文的作者,也需要同学们来一起共建,方式包括但不限于:使用并提出建议,PR,Star,转发等。


JS中反应式数据存储方案对比 Edata与RxJS, Mobx 同学, 如果觉得本文对你有帮助, 欢迎打赏一杯咖啡~

JS中反应式数据存储方案对比 Edata与RxJS, Mobx