Redux 進階 -- 編寫和使用中介軟體

阿希發表於2018-04-23

本文目標:和大家探討一下如何通過編寫和使用 redex 中介軟體 來幫助我們更好的使用 redux

例子

在上一篇文章 Redux 進階 -- 優雅的處理 async action 中,阿大通過改善流程對接完成了水果店的升級。

但是阿大又有一個新的想法,他想詳細的看看每一個顧客的購買需求來了之後,賬本的前後變化。看來又要加一個新角色記錄員了。難道要像加採購員那樣手動的一個個的加嗎?那可太麻煩了。正好阿大發現 redux 裡有一個功能就是中介軟體。中介軟體是幹嘛的呢?簡而言之,就是把顧客的需求從銷售員收銀員之間加上各種角色來處理。每一個角色就是一箇中介軟體。接下來阿大就開始來寫中介軟體了。

redux 中介軟體寫起來其實很簡單,就是一個函式而已。按照它的要求。這個函式接受一個 { dispatch, getState } 物件作為引數,然後返回一個 action

那這樣,就可以把原來的採購員也改造成中介軟體了,其實採購員就是拿到了顧客需求之後讓顧客的需求延遲 dispatch,這用延遲用函式就可以做到了:

// next 是用中介軟體增強之後的 dispatch
// dispatch 是最原始的 store.dispatch
const thunkMiddleware = ({ dispatch }) => next => action => {
  if (typeof action === 'function') {

    // 函式形式的 action 就把 dispatch 給這個 action,讓 action 決定什麼時候 dispatch (控制反轉)
    return action(dispatch);
  }

  // 普通的 action 就直接傳遞給下一個中介軟體處理
  return next(action);
}
複製程式碼

然後我們就需要把原來的顧客需求改一下了:

// 買水果 - 進口蘋果
function buyImportedApple(num) {

  // 返回一個函式型別的 action,這個函式接受 dispatch,可以決定什麼時候 dispatch
  return dispatch => API.fetchImportedApple(() => dispatch({
    type: 'BUY_IMPORTED_APPLE',
    payload: num
  }));
}

// 買生鮮 - 進口雞蛋
function buyImportedEgg(num) {
  return dispatch => API.fetchImportedEgg(() => dispatch({
    type: 'BUY_IMPORTED_EGG',
    payload: num
  }));
}
複製程式碼

然後採購員就可以只負責採購了,改回去:

// 採購商品生成器,不同的商品需要不同的時間採購
function fetching(time, callback) {

  // 用延時模擬採購時間
  const timer = setTimeout(() => {
    clearTimeout(timer);

    // 採購完成,通知銷售員
    callback();
  }, time);
}

// 採購進口蘋果需要 2 天(2s)
function fetchImportedApple(callback) {
  fetching(2000, callback);
}

// 採購進口蘋果需要 3 天(3s)
function fetchImportedEgg(callback) {
  fetching(3000, callback);
}

// 採購員
const API = {
  fetchImportedApple, // 採購進口蘋果
  fetchImportedEgg // 採購進口雞蛋
}
複製程式碼

然後,我們在新增一個記錄員的中介軟體:

const loggerMiddleware = ({ getState }) => next => action => {
  console.log(`state before: ${JSON.stringify(getState())}`);
  console.log(`action: ${JSON.stringify(action)}`);
  const result = next(action);
  console.log(`state after: ${JSON.stringify(getState())}`);
  console.log('================================================');
  return result;
}
複製程式碼

刪除掉原來的監聽:

- store.subscribe(() => console.log(JSON.stringify(store.getState())));
複製程式碼

好了,接下來就可以通過 reduxapplyMiddleware 來串聯起這些中介軟體啦。

const { createStore, combineReducers, applyMiddleware } = require('redux');

// 中介軟體的呼叫順序是從右到左
const store = createStore(reducer, applyMiddleware(thunkMiddleware, loggerMiddleware));
複製程式碼

好了,大功告成,開始服務顧客:

store.dispatch(buyApple(3));
store.dispatch(buyImportedApple(10));
store.dispatch(buyEgg(1));
store.dispatch(buyImportedEgg(10));
store.dispatch(buyApple(4));
store.dispatch(buyImportedApple(10));
store.dispatch(buyEgg(8));
store.dispatch(buyImportedEgg(10));
// state before: {"fruit":{"apple":0,"importedApple":0},"fresh":{"egg":0,"importedEgg":0}}
// action: {"type":"BUY_APPLE","payload":3}
// state after: {"fruit":{"apple":3,"importedApple":0},"fresh":{"egg":0,"importedEgg":0}}
// ================================================
// state before: {"fruit":{"apple":3,"importedApple":0},"fresh":{"egg":0,"importedEgg":0}}
// action: {"type":"BUY_EGG","payload":1}
// state after: {"fruit":{"apple":3,"importedApple":0},"fresh":{"egg":1,"importedEgg":0}}
// ================================================
// state before: {"fruit":{"apple":3,"importedApple":0},"fresh":{"egg":1,"importedEgg":0}}
// action: {"type":"BUY_APPLE","payload":4}
// state after: {"fruit":{"apple":7,"importedApple":0},"fresh":{"egg":1,"importedEgg":0}}
// ================================================
// state before: {"fruit":{"apple":7,"importedApple":0},"fresh":{"egg":1,"importedEgg":0}}
// action: {"type":"BUY_EGG","payload":8}
// state after: {"fruit":{"apple":7,"importedApple":0},"fresh":{"egg":9,"importedEgg":0}}
// ================================================
// state before: {"fruit":{"apple":7,"importedApple":0},"fresh":{"egg":9,"importedEgg":0}}
// action: {"type":"BUY_IMPORTED_APPLE","payload":10}
// state after: {"fruit":{"apple":7,"importedApple":10},"fresh":{"egg":9,"importedEgg":0}}
// ================================================
// state before: {"fruit":{"apple":7,"importedApple":10},"fresh":{"egg":9,"importedEgg":0}}
// action: {"type":"BUY_IMPORTED_APPLE","payload":10}
// state after: {"fruit":{"apple":7,"importedApple":20},"fresh":{"egg":9,"importedEgg":0}}
// ================================================
// state before: {"fruit":{"apple":7,"importedApple":20},"fresh":{"egg":9,"importedEgg":0}}
// action: {"type":"BUY_IMPORTED_EGG","payload":10}
// state after: {"fruit":{"apple":7,"importedApple":20},"fresh":{"egg":9,"importedEgg":10}}
// ================================================
// state before: {"fruit":{"apple":7,"importedApple":20},"fresh":{"egg":9,"importedEgg":10}}
// action: {"type":"BUY_IMPORTED_EGG","payload":10}
// state after: {"fruit":{"apple":7,"importedApple":20},"fresh":{"egg":9,"importedEgg":20}}
// ================================================
複製程式碼

上面我們寫的兩個中介軟體其實就是 redux-thunkredux-logger 的簡版。在實際中,推薦使用它們,會更可信。

講解

編寫 redux 中介軟體需要按照要求來,返回這樣的函式

// 中介軟體接受一個物件,裡面有原始的 dispatch,和 getState 方法用於獲取 state
// 中介軟體函式返回一個函式,這個函式接受一個 next 引數,這個 next 是下一個中介軟體要做的事情 action => { ... }
function thunkMiddleware({ dispatch, getState }) {
  return function(next) {
    return function(action) {
      // 做你的事情
    }
  }
}
複製程式碼

圖解

Redux 進階 -- 編寫和使用中介軟體

程式碼地址:Redux 進階 -- 編寫和使用中介軟體,直接控制檯執行 node ./demo5/index.js 檢視效果

上一篇:Redux 進階 -- 優雅的處理 async action

下一篇:Redux 高階 -- 原始碼分析

相關文章