iT邦幫忙

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

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

[ngrx/store-12] Store 加完整的 Reducer

Store 加完整的 Reducer

現實的狀態跟 Reducer 通常都有幾個以上,我們來看一下多個 Reducer 的例子

多個(multiple) Reducer

const login = (state = false, action) => {
  switch (action.type) {
    case 'LOGIN':
      return true;
    case 'LOGOUT':
      return false;
    default:
      return state;
  }
}

const messages = (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;
  }
}
const myReducer = {login, messages};       // object of two reducer functions

承前面的例子我們再加上一個簡單的 login reducer, 這個 reducer 狀態很單純,是一個布林值 (boolean) 表示使用者登入狀態,這時候的 myReducer 是一個物件,它的內容 key 是 loginmessages,value 是兩個函數 ((state, action) => state),而狀態樹會是

inteface State {
    login: boolean;
    messages: message[];
}

interface message {
    id: number;
    msg: string;
}

結合多個 Reducer

我們用下列的程式來應用這個多重的 Reducer 到這個多重的狀態樹

const combineReducer = reducers => (state, action) => {
  return Object.keys(reducers).reduce((nextState, key) => {
    nextState[key]=reducers[key](state[key], action);
    return nextState;
  }, {})
}

const rootReducer = combineReducer(myReducer);

稍微說明一下

  1. Object.keys(reducers) 是取用 Reducers(myReducer) 這個 Object 的 keys, 也就是 loginmessage 在這個例子中。
  2. 我們再次看到 .reduce() 這個 Operator, 其實可以將它當成 for-loop,差別在於他會累積結果,也就是累積新的狀態,initial 從 {} 開始。
  3. 第一個 key 是 login, 所以它會取 state[key] 也就是 login 的狀態,加上 action應用於 reducers[key] 就是 login reducer 的函數 ((state, action) => state),產生新的 login nextState。.reduce() 將這個新的狀態加入 {}
  4. 第二個 key 是 message, 會用 state[key] 也就是 messages 的舊狀態加入 action 應用 reducer[key] 就是 messages reducer 的函數 ((state, action) => state), 產生 messages 新的狀態,.reduce() 將這個新的狀態加入上面的物件。
  5. .reduce() 將新生的狀態一個一個加入 {},產生新的狀態樹。

看到這裡,大家大概明瞭為什麼叫它 Reducer 了,在一個更複雜的狀態樹中,Reducer 負責將一個個狀態應用到對應的函數中,再將它們包成一個狀態樹

將 Reducer 加入 Store

有了 Reducer, 我們在將它加入 Store 的架構中,就成了完整的 Store

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

class Dispatcher extends Rx.Subject<Action> {
  dispatch(act) {
      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);
  }
}

const login = (state = false, action) => {
  switch (action.type) {
    case 'LOGIN':
      return true;
    case 'LOGOUT':
      return false;
    default:
      return state;
  }
}

const messages = (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;
  }
}

const myReducer = {login, messages};       // object of two reducer functions
const combineReducer = reducers => (state, action) => {
  return Object.keys(reducers).reduce((nextState, key) => {
    nextState[key]=reducers[key](state[key], action);
    //console.log('nextState is ', nextState);
    return nextState;
  }, {})
}

const rootReducer = combineReducer(myReducer);

// instanciate new store
const initialState = {login: false, messages: []};
const dispatcher = new Dispatcher();
const store = new Store(dispatcher, rootReducer, initialState);

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

// start dispatch action
store.dispatch({type: 'LOGIN'});   // dispatch new action to store
store.dispatch({type: 'LOGOUT'});  // dispatch another action

store.dispatch({type: 'ADD_MESSAGE', payload: {id: 1, msg: 'First Message'}});
store.dispatch({type: 'ADD_MESSAGE', payload: {id: 2, msg: 'Second Message'}});
store.dispatch({type: 'REMOVE_MESSAGE', payload: 1});

codepen

部分輸出如下
https://ithelp.ithome.com.tw/upload/images/20171228/20103574TzLXCMvVO2.png

有了完整的 Reducer, 接下來我們來看 store.select() 要怎麼做?


上一篇
[ngrx/store-11] Store 架構加入最簡單的 Reducer
下一篇
[ngrx/store-13] Store 加入 select
系列文
ngrx/store 4 學習筆記30

尚未有邦友留言

立即登入留言