iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 11
0
Modern Web

ngrx/store 4 學習筆記系列 第 11

[ngrx/store-11] Store 架構加入最簡單的 Reducer

Store 架構加入最簡單的 Reducer

Reducer 是什麼?

如果對於 Observable 的 .reduce .scan 不熟的話,建議回頭看一下[ngrx/store -3] Observable 的 運算子 (Operator)。基本上.scan() 會一直持續做,而 reduce() 會等到 complete 時才一次將結果產生,而我們要的 Reducer 其實是一直循環持續做而且能用到前面的狀態,用 .scan() 來做 Reducer 是最適當的 Operator。

先從 Reducer 的輸出跟輸入看起 https://ithelp.ithome.com.tw/upload/images/20171227/20103574PMIGu3nNb0.png

Reducer 是純函數

記得我們在 [ngrx/store-7] 純函數 (Pure Function) 以及 [ngrx/store-8] Javascript Mutable 跟 Immutable 資料型態談過純函數,如果您還沒看過這兩篇文章,建議您花些時間看一下,我們來寫一個最簡單的 Reducer

// reducer
const message = (state = [], action) => {
  switch (action.type) {
    case 'ADD_MESSAGE': 
      return [...state, action.payload];
    case 'REMOVE_MESSAGE':{
      return state.filter(msg => msg.id != action.payload);
    }
    default:
      return state;
  }
}

這裡我們的狀態樹是一個物件的陣列,當增加一筆物件到陣列時(ADD_MESSAGE),我們使用擴展語法 ... 拷貝原來陣列,再將新的物件放入陣列中,可以放在前面也可以放在後面,請記得在這裡我們不可以使用陣列的.push(),因為這樣做會改變原來的陣列,造成副作用。在 ngrx/store 並不會強制您這樣做,所以這是程式的紀律問題,有了這個紀律,往後系統複雜時,就比較不會產生莫名其妙的 Bug.

在刪除一筆資料時(REMOVE_MESSAGE),我們使用了 .filter(),這個陣列的 Operator 是可以使用的,因為它會產生新的陣列,不會改變原來的陣列。

將 Reducer 註冊 (register) 到 Store 中

接著我們將這個 Reducer 註冊給 Store 使用,整個程式如下

interface Action {
  type: string;
  payload?: any
}

class Dispatcher extends Rx.Subject<Action> {
  dispatch(act) {
      //console.log('got dispatch ', act.type);
      this.next(act);
  }
}

class Store extends Rx.BehaviorSubject<Action> {
  constructor(private dispatcher, private reducer, initialState) {
    super(initialState);
    this.dispatcher
      .do((v) => { /*console.log('do some effect for', v.type) */})    
      .scan((s, v) => this.reducer(s, v), initialState)
      .subscribe(state => {
        super.next(state);  // new state, push to subscriber
      });
  }
  dispatch(act) {  // delegate to dispatcher
    this.dispatcher.dispatch(act);
  }
  // override next to allow store subscribe action$
  next(act) {
    this.dispatcher.dispatch(act);
  }
}

// reducer
const message = (state = [], action) => {
  switch (action.type) {
    case 'ADD_MESSAGE': 
      return [...state, action.payload];
    case 'REMOVE_MESSAGE':{
      return state.filter(msg => msg.id != action.payload);
    }
    default:
      return state;
  }
}


// instanciate new store
const initialState = [];
const dispatcher = new Dispatcher();
const store = new Store(dispatcher, message, initialState);

// add subscriber
const sub1 = store.subscribe(v => console.log('messages ===> ', v));

// start dispatch action
store.dispatch({type: 'ADD_MESSAGE', payload: {id: 1, message: 'First Message'}});
store.dispatch({type: 'ADD_MESSAGE', payload: {id: 2, message: 'Seconde Message'}});
store.dispatch({type: 'REMOVE_MESSAGE', payload: 2});
store.dispatch({type: 'ADD_MESSAGE', payload: {id: 3, message: 'Third Message'}});

codepen

它的輸出如下
https://ithelp.ithome.com.tw/upload/images/20171227/20103574oDapJ7z8xy.png

上面的程式架構還是延續原本的 Store 大架構,我們增加了一個參數 reducer,dispather 會將新的 Action 交給 .scan() 這個 Operator, 由 reducer 負責將就的狀態跟Action 處理完產生新的狀態。因為使用了 .scan(),整個循環會一直持續使用這個 reducer 來處理 Action,而且會用到前面的狀態。

實際上的 Reducer 會更複雜,這裡我們只有單一個狀態,單一個 Reducer, 那麼如果兩個以上的 Reducer 呢?還有如何處理狀態樹呢?我們接下來看。


上一篇
[ngrx/store-10] Store 的大架構
下一篇
[ngrx/store-12] Store 加完整的 Reducer
系列文
ngrx/store 4 學習筆記30

尚未有邦友留言

立即登入留言