RxJS observable V.S Mobx observable
這次不是寫 Angular 切版實戰,因為.…..內容實在太龐大了有點難寫(光想要寫個「 如何用 Angular 建立 high order component 」就讓人很頭大)。所以決定先來開小差,寫最近遇到如何用 observable 來處理 Data Flow 的問題。
然後,最近也開始寫 React component,結果發現超好寫(自爆),不禁覺得 Angular 真的要再多加油,如果世界觀再打平一點就好了QQ。
這篇文章建議先有 observable (一種處理非同步工作的工具)的概念再來閱讀,可先看以下文章,他的實例非常好懂:
以前在處理非同步的工作、資料傳遞(像是跟 server API 拿資料、等待結果回傳再進行下一步動作),常用的有 promise 和 async await,observable 跟他們最大的差異,就是能夠
- 實現 functional,取得 response 後可直接進行資料 parsing ,直觀好懂
- 實現 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。