1. 程式人生 > >對於react-thunk中介軟體的簡單理解

對於react-thunk中介軟體的簡單理解

前言

剛來公司的時候,對react專案中的thunk中介軟體的作用一直不太瞭解,最近有時間決定好好研究一下。鑑於本人初次寫部落格,並已假設讀者已掌握redux的一些基本用法;如有錯誤,還望指出。不勝感激!

首先簡單回顧一下redux工作流程

圖畫的不太好,見諒;
對於reactUI元件來說,資料的來源無外乎兩種,一種是使用者主動觸發的動作,例如點選事件、提交表單,輸入操作;另一種是元件主動的資料更新,如獲取頁面初始資料,子元件接受父元件的props變化而進行更新檢視操作;
如圖所示,無論那種對於資料的操作,對於view都會派發出一個action

狀態的更新

正如我們所知,在redux裡,每次更新後的Store都會對應著一個新的view,而Store裡面資料的更新依賴action的觸發————Store.dispatch(action)會自執行初始化中createStore中注入的reducers,從而計算出新的狀態。

import { createStore } from 'redux'
//reducer 計算狀態的純函式
//initialState 初始化資料
//enhancers中介軟體
createStore(reducers, initialState, enhancers)

action的使用和外掛的擴充套件

對於元件的輸入操作(如點選事件),可以將store.dispatch(action)繫結到元件

 const store = createStore(reducer);
 const TodoList = ({ state, someActionCreator }) => (
	    <
ul> {state.map(someState => <Todo key={someState.someData} onClick={() => store.dispatch(someActionCreator(state.someData))} /> </ul> )

或者通過connect方法,從元件的props中拿到dispatch方法,發出一個action

  //  將state注入到元件的props裡
// 注意,這裡的state指的是redux管理的資料,每一個view的狀態對應著 // 唯一的state; // state的集合就是redux管理的store const mapStateToProps = store => ({ state: store.state })// 將action注入到元件的props 裡 const mapDispatchToProps = dispatch => ({ actions: state => dispatch(actionCreators(state)) })export default connect( mapStateToProps, mapDispatchToProps )(TodoList)

然後元件繫結事件就可以改成這樣 ,( actionCreators用於生成action, 參考官方連結 https://redux.js.org/basics/actions)

 const TodoList = ({ state, actions }) => (
    `<ul>
        {state.map(someState =>
           <Todo
               key={someState.someData}
               onClick={() => actions(someState.someData)}
         />
        </ul>`
        ) 

那麼問題來了,dispatch是同步執行reducers生成新狀態的,對於頁面的操作沒有問題;但是如果點選事件是請求了某個結果,需要等待結果響應後再更新檢視呢?應該如何處理?

因而redux引入了thunk中介軟體,對action進行了擴充套件

##thunk中介軟體解決了什麼問題?
引入thunk外掛後,我們可以在actionCreators內部編寫邏輯,處理請求結果。而不只是單純的返回一個action物件。

//未引入前的寫法
let nextTodoId = 0
export const addTodo = text => ({
     type: 'ADD_TODO',
     id: nextTodoId++,
     text
 })
 
 
 //引入thunk後
 let nextTodoId = 0
 export const addTodo = text => ({
     return async dispatch => {
     //dosomething, request
      await request()
      dispatch({
	     type: 'ADD_TODO',
	     id: nextTodoId++,
	     text
		})
     }
 })

thunk中介軟體的使用方法

import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';

const store = createStore(
  reducer,
  applyMiddleware(thunk)
);

createStore其實可以接受三個引數,第二個引數preloadedState一般作為整個應用的初始化資料,如果傳入了這個引數,applyMiddleware就會被當做第三個引數處理

const store = createStore(
  reducer,
  initialState,
  applyMiddleware(thunk)
);

中介軟體都要放到applyMiddleware裡,如果要新增中介軟體,可以依次新增,但是要遵循文件定義的順序

const store = createStore(
  reducer,
  initialState,
  applyMiddleware(thunk,middleware1, middleware2)
);

原始碼解讀

也許你會奇怪,為什麼使用的時候要按照上面的寫法,那我們就一起看下方法的實現

首先是createStore的引數順序
function createStore(reducer, preloadedState, enhancer) {
  var _ref2;

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.');
    }

    return enhancer(createStore)(reducer, preloadedState);
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.');
  }

第一個判斷已經告訴了我們答案,引數的型別檢驗結果決定了順序

applyMiddleware是幹什麼用的
function applyMiddleware() {
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function () {
      for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
        args[_key2] = arguments[_key2];
      }

      var store = createStore.apply(undefined, args);
      var _dispatch = function dispatch() {
        throw new Error('Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');
      };

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch() {
          return _dispatch.apply(undefined, arguments);
        }
      };
      var chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

程式碼不多,而且非常清晰:
1、applyMiddleware顧名思義,用於呼叫各種中介軟體;
2、applyMiddleware執行後,將所有入參中介軟體存入一個數組,並且返回一個閉包(閉包的概念不做累述)
3、閉包接受一個createStore作為入參並且執行後返回下一個閉包,createStore這個入參有沒有很眼熟,沒錯,就是redux的createStore。

返回結果

返回將所有中介軟體串聯存入的dispatch,執行時從右向左執行,第一次的執行結果會返回給一下個,依次類推。

如何實現每個中介軟體串聯執行

_dispatch = compose.apply(undefined, chain),使用了一個compose函式,呼叫之後就可以將所有中介軟體串聯起來,那麼compose又是如何實現的呢?

精華所在

function compose() {
  for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
    funcs[_key] = arguments[_key];
  }

  if (funcs.length === 0) {
    return function (arg) {
      return arg;
    };
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce(function (a, b) {
    return function () {
      return a(b.apply(undefined, arguments));
    };
  });
}

個人認為這個compose函式是整個redux中非常亮眼的部分,短短几行程式碼,就完成了一個核心功能的擴充套件,是責任鏈設計模式的經典體現。

ALSO 我們也可以使用這個compose方法對applyMiddleware進行擴充套件

let devtools = () => noop => {
          console.log(noop);
          return noop;   //createStore
      };
const enhancers = [
    applyMiddleware(...middleware),
    devtools()
  ];
createStore(reducers, initialState, compose(...enhancers));

然後回來,我們就明白了createStore中的設計

//如果存在中介軟體引數,那麼將會得到一個經過改裝的dispatch
// return _extends({}, store, {dispatch: _dispatch});
if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.');
    }

    return enhancer(createStore)(reducer, preloadedState);
  }
dispatch經過了怎樣的改裝

如上已經說過,compose會將傳入的函式陣列從右向左串聯執行

compose.apply(undefined, chain)(store.dispatch);

thunk一定會接受上一個中介軟體的執行結果繼續執行,然後最終在createState裡返回一個改造好的dispatch, 接下來我只要看下thunk是怎樣實現的,就瞭解了整個中介軟體使用的原理:

function createThunkMiddleware(extraArgument) {
  return function (_ref) {
    var dispatch = _ref.dispatch,
        getState = _ref.getState;
    return function (next) {
	        //最終的dispatch
	        //next就是接收的store.dispatch引數,為上一個中介軟體改造過的dispatch
      return function (action) {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }

        return next(action);
      };
    };
  };
}

var thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

程式碼同樣精煉,改造後的dispatch入參接受的資料型別:
1、非function,不處理,將action 傳給下一個中介軟體,最終都會根據傳入的action計算相應的reducers(開頭說的自執行)————store.dispatch(action)
2、function型別的action, 自動觸發函式,並且將store.dispatch傳入

總結

再結合開始介紹的thunk用法,我們就明白了thunk的原理,可以在actionCreators裡通過返回一個函式,然後就可以在函式裡編寫某些非同步操作了,待非同步操作結束,最後通過傳入的store.dispatch,發出action通知給Store要進行狀態更新。