RxJS observable V.S Mobx observable

可觀測資料流的兩大工具PK

這次不是寫 Angular 切版實戰,因為.…..內容實在太龐大了有點難寫(光想要寫個「 如何用 Angular 建立 high order component 」就讓人很頭大)。所以決定先來開小差,寫最近遇到如何用 observable 來處理 Data Flow 的問題。

然後,最近也開始寫 React component,結果發現超好寫(自爆),不禁覺得 Angular 真的要再多加油,如果世界觀再打平一點就好了QQ。

這篇文章建議先有 observable (一種處理非同步工作的工具)的概念再來閱讀,可先看以下文章,他的實例非常好懂:

以前在處理非同步的工作、資料傳遞(像是跟 server API 拿資料、等待結果回傳再進行下一步動作),常用的有 promise 和 async await,observable 跟他們最大的差異,就是能夠

  1. 實現 functional,取得 response 後可直接進行資料 parsing ,直觀好懂
  2. 實現 change listener,一旦訂閱,即可監聽未來所有數值的改變

RxJS observable 用起來像這樣:

export class DataStore {
  public data: BehaviorSubject<number> = new BehaviorSubject(0);

  update(newValue: number) { data.next(newValue); }
}

export class ButtonComponent {
  public data: number;
  constructor(private dataStore: DataStore) {
dataStore.data.subscribe(x => this.data = x);
  }

  onBtnClick() { this.dataStore.update(this.data + 1); }
}

由以上可知,我們在 DataStore 先設定了一個可供訂閱的 dataSubject、初始值為 0。ButtonComponent 在 constructor運作後就會訂閱 dataSubject 的所有變動,一旦變動就把最新的數值存到 data 裡面, 當按鈕按下去,會去呼叫 dataStore.update() 讓 dataSubject 存放的數值加一,進而觸發 data改變。

這裡要注意到一點是,dataStore.dataSubject 只要有任何更新,所有訂閱他的元件都會拿取到最新的數值,這就是 observable 強大的地方,將所有與 dataStore 相關連的元件的資料溝通綁定在一起,不會再有一個動作改 A 元件結果沒改到 B 元件的情況。

Mobx observable 用起來像這樣:

export class DataStore {
  @observable public data: number;

  @action
  update(newValue: number) { this.data = newValue; }
}

export class ButtonComponent {
  public data: number;

  constructor(private dataStore: DataStore) {
    observe(dataStore, 'data', x => this.data = x.newValue);
  }

  onBtnClick() { this.dataStore.update(this.data + 1); }
}

看起來 Mobx 較為直觀好懂,因為他藉由 decorator @ 讓變數有了可被觀測的能力。這裡注意一下我沒有使用 observe(dataStore.data, ...) 因為 observe 只支援觀測物件!如果要觀測 primitive value 要使用 observable box ,文章之後也會提到。


但接下來,將會遇到 Mobx 的弱點:
我們設定一個情境,要觀測資料開始 loading 與 loading完成的過程,加入isDataLoading 標記著資料開始改變到改變結束的狀態。

RxJS

export class DataStore {
  public dataSubject: BehaviorSubject<number> = new BehaviorSubject(0);

  update(newValue: number) { dataSubject.next(newValue); }
}

export class ButtonComponent {
  public data: number;
  public isDataLoading: boolean;

  constructor(private dataStore: DataStore) {
    dataStore.dataSubject.subscribe(x => {
      this.data = x;
      this.isDataLoading = false;
    });
  }

 onBtnClick() {
    this.isDataLoading = true;
    this.dataStore.update(this.data);
  }
}

Mobx

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);
  }
}

這裡會有個情況導致錯誤,observe 只有在 dataStore.data 改變時才會觸發,當我們執行 this.dataStore.update(this.data)因為數值沒改變,observe 無法觸發,將導致 isDataLoading 一直為 true。

必須改寫成:

export class DataStore {
  public get data(): number { return this.dataObserve.get(); }
  public set data(v: number) { this.dataObserve.set(v); }
  public dataObservable: IObservableValue<number> = observable.box(0);
  
  update(newValue: number) { this.data = newValue; }
}

export class ButtonComponent {
  public data: number;
  public isDataLoading: boolean;


constructor(private dataStore: DataStore) {
    observe(dataStore.dataObservable, x => {
      this.data = x.newValue;
      this.isDataLoading = false;
    });
  }

onBtnClick() {
    this.isDataLoading = true;
    this.dataStore.update(this.data);
  }
}

這裡可看見 dataObservable 是個可觀測的 box 物件,便能使用observe(dataStore.dataObservable, ...) 並且不論數值是否更新,dataObserve.set(v) 都會觸發 observe 變動。


綜合以上想做個結論,一般在處理單純的資料同步上,Mobx 提供相當簡易好用的 coding style,而若牽涉到動作開始與結束、並非單純只要觀測資料數值的情境下,RxJS 提供了較簡單的作法。可在不同的情境上,自由取用合適的工具。譬如 data cache,user 不需知道 data cache 何時開始與結束,就可優先考慮 Mobx ;而頁面資料 API,幾乎都要提示 user 並在載入前後顯示 loading 狀態,就可考慮 observable。

參考資料

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *