redux的中間件

1、什麼是中間件?
  顧名思義就是在一個執行流程中間用的一個組件,截住過路的而後對其進行控制加強的操做來知足咱們的需求。
 
    那redux爲何須要用中間件呢?咱們看一下redux的數據流向
    view -> action -> reducer -> state ->view
    view觸發事件觸發action,action中根據type把數據傳到對應的reduce中,而後reducer拿到數據返回新的state。
 
    這流程就像從view給出一個訂單,而後貨車立刻拿着訂單根據在action的這條路上不停的開到對應的reducer中,而後在reducer中卸貨,對貨物處理入庫,而後車子再把處理後新的貨品運回view中展現。當咱們的操做都是同步的時候,這個流程毫無問題,可是當咱們的須要異步獲取數據來更新的時候就發現有問題了,即咱們的車子已經出發了,可是要給reducer運的貨還須要等一下才能到,等貨備好了但車子已經跑沒影了。
 
    那咱們要怎麼辦呢加快備貨嗎,顯然是想要加快異步操做是沒有用的,畢竟車子一聽到指令就開了,在開的同時備好貨是來不及的,由於你的貨在車外。而reducer是一個純函數的數據處理點,是一個加工中心,人家哪裏知道你的貨是啥。因此最終咱們要想辦法攔住車子,等貨備好裝上車才讓車子繼續開往reducer,那咱們就在action->reducer這個環節中間進行攔截住車子,在這個攔截住的過程當中咱們還能夠對車子隨心所欲了,好比日誌控制,調用異步請求、路由控制等等。
    而後咱們的流程就成爲以下路徑
    view -> action ->middlewire -> reducer -> state ->view
    舉完這個例子應該也大概知道爲何redux處理異步數據須要用一箇中間件了,那接下來咱們來看看經常使用的redux中間件 
 
2、Redux-thunk
  npm安裝該包,而後直接將redux-thunk引入到createStore操做的文件中
1 import { applyMiddleware, createStore } from 'redux';
2 import thunk from 'redux-thunk';
3  const store = createStore(
4   reducers, 
5   applyMiddleware(thunk)
6 );
   直接將thunk中間件引入,放在applyMiddleware方法之中,傳入createStore方法,就完成了store.dispatch()的功能加強。便可以在reducer中進行一些異步的操做。 applyMiddleware() 其實applyMiddleware就是Redux的一個原生方法,將全部中間件組成一個數組,依次執行。 中間件多了能夠當作參數依次傳進去。 
  redux-thunk源碼:(路徑node_modules/redux-thunk/src/index.js)   
 1 function createThunkMiddleware(extraArgument) {
 2      return ({ dispatch, getState }) => next => action => {
 3       if (typeof action === 'function') {
 4           return action(dispatch, getState, extraArgument);
 5       }
 6       return next(action);
 7      };
 8 } 
 9 const thunk = createThunkMiddleware();
10 thunk.withExtraArgument = createThunkMiddleware; 
11 export default thunk;

redux-thunk中間件export default的就是createThunkMiddleware()過的thunk,再看createThunkMiddleware這個函數,返回的是一個柯里化過的函數。咱們再翻譯成ES5的代碼容易看一點html

 1 function createThunkMiddleware(extraArgument) {
 2   return function({ dispatch, getState }) {
 3     return function(next){
 4       return function(action){
 5         if (typeof action === 'function') {
 6           return action(dispatch, getState, extraArgument);
 7         }
 8         return next(action);
 9       };
10     }
11   }
12 }

  能夠看出來redux-thunk最重要的思想,就是能夠接受一個返回函數的action creator。若是這個action creator 返回的是一個函數,就執行它,若是不是,就按照原來的next(action)執行。 正由於這個action creator能夠返回一個函數,那麼就能夠在這個函數中執行一些異步的操做。例如:node

 1 export function addCount() {
 2   return {type: ADD_COUNT}
 3 } 
 4 export function addCountAsync() {
 5   return dispatch => {
 6     setTimeout( () => {
 7       dispatch(addCount())
 8     },2000)
 9   }
10 }

  addCountAsync函數就返回了一個函數,將dispatch做爲函數的第一個參數傳遞進去,在函數內進行異步操做就能夠了。固然爲了不在異步請求內去使用dispatch,以致於會出現回調地獄的狀況,咱們固然使用promise來處理,那就就得加上promiseMiddleware中間件來處理promise。爲了能在更好的觀察監控咱們的action,能夠用createLogger中間件來打印action日誌。
 
  示例代碼:
  store.js
 1 import { createStore, applyMiddleware } from 'redux';
 2 import thunkMiddleware from 'redux-thunk';
 3 import combineReducers from '../reducers/reducers';
 4 import promiseMiddleware from 'redux-promise'
 5 import { createLogger } from 'redux-logger';
 6 import createHistory from 'history/createBrowserHistory';
 7 const history = createHistory();
 8 const arr = [thunkMiddleware,promiseMiddleware];
 9 if (process.env.NODE_ENV !== 'pro') arr.push(createLogger({
10     diff: true,
11     collapsed: true,
12 }));
13 let store = createStore(combineReducers, applyMiddleware(...arr));
14 export default store;

  action.jsreact

1 import types from '../store/types';
2 import {
3     get_user_info //題主是本地配的mock服務,也能夠用一個定時器返回數據實現異步
4 } from '../api/userInfo'
5 export const getUserInfo = () => (dispatch) =>
6 dispatch({
7     type: types.GET_USER_INFO_REQUEST,
8     payload: Promise.resolve(get_user_info())
9 })

  reducer.jsnpm

 1 import types from '../store/types';
 2 import { reducerCreators } from '../util/index';
 3 const initState = {
 4     isLoading: false,
 5     userInfo: {},
 6     errorMsg: ''
 7 };
 8 
 9 export default reducerCreators(initState, {
10     [`${types.GET_USER_INFO_REQUEST}`]: (state, data) => {
11         return Object.assign({}, state, {
12             userInfo: data.data
13         })
14     }
15 })

   reducerCreators (../util/index) 從新生成reducer使其符合範式redux

 1 export function reducerCreators (initialState, actionTypeMapList) {
 2     return (state = initialState, action) => {
 3     const reducerInstance = typeof actionTypeMapList === 'object' &&             
 4         actionTypeMapList[action.type] ? 
 5         actionTypeMapList[action.type](state, action.payload ? 
 6         action.payload : {}, action.params) : 
 7         state;
 8     return reducerInstance;
 9     };
10 }

  view.jsapi

 1 import React, { Component } from 'react';
 2 import { connect } from 'react-redux';
 3 import { bindActionCreators } from 'redux'
 4 import { getUserInfo } from 'actions/userInfo';
 5 class UserInfo extends Component {
 6     render() {
 7     const { userInfo, isLoading, errMsg } = this.props.userInfo;
 8         return (<div>
 9             {
10                 isLoading ? '請求中' :
11                 (errMsg ? errMsg :
12                     <div>
13                         <p>用戶信息:</p>
14                         <p>用戶名:{userInfo.name}</p>
15                         <p>介紹:{userInfo.intro}</p>
16                     </div>
17                 )
18             }
19             <button onClick={() => this.props.getUserInfo()}>請求用戶信息</button>
20         </div>)
21     }
22 }
23 
24 const mapStateToProps = (state) => {
25     return {
26         userInfo: state.userInfo,
27     }
28 }
29 
30 const mapDispatchToProps = (dispatch) => {
31     return {
32         getUserInfo: bindActionCreators(getUserInfo, dispatch)
33     }
34 }
35 
36 export default connect(mapStateToProps, mapDispatchToProps)(UserInfo);

 

3、Redux-saga數組

  redux-saga 是一個用於管理應用程序 Side Effect(反作用,例如異步獲取數據,訪問瀏覽器緩存等)的 library,它的目標是讓反作用管理更容易,執行更高效,測試更簡單,在處理故障時更容易。
  能夠想像爲,一個 saga 就像是應用程序中一個單獨的線程,它獨自負責處理反作用。 redux-saga 是一個 redux 中間件,意味着這個線程能夠經過正常的 redux action 從主應用程序啓動,暫停和取消,它能訪問完整的 redux state,也能夠 dispatch redux action。
  redux-saga 使用了 ES6 的 Generator 功能,讓異步的流程更易於讀取,寫入和測試。(若是你還不熟悉的話, 這裏有一些介紹性的連接) 經過這樣的方式,這些異步的流程看起來就像是標準同步的 Javascript 代碼。(有點像 async/await,但 Generator 還有一些更棒並且咱們也須要的功能)。
  你可能已經用了 redux-thunk 來處理數據的讀取。不一樣於 redux thunk,你不會再遇到回調地獄了,你能夠很容易地測試異步流程並保持你的 action 是乾淨的。
  示例
  store.js
 1 import { createStore, applyMiddleware } from 'redux';
 2 import combineReducers from '../reducers/reducers';
 3 import promiseMiddleware from 'redux-promise'
 4 import createSagaMiddleware from 'redux-saga'
 5 import mySaga from './saga'
 6 import { createLogger } from 'redux-logger';
 7 const sagaMiddleware = createSagaMiddleware(mySaga)
 8 const arr = [sagaMiddleware];
 9 arr.push(promiseMiddleware)
10 if (process.env.NODE_ENV !== 'pro') arr.push(createLogger({
11     diff: true,
12     collapsed: true,
13 }));
14 export const store = createStore(combineReducers, applyMiddleware(...arr));
15 sagaMiddleware.run(mySaga)

  這裏saga也用npm安裝一下,而後像和thunk同樣的引入到store中,不一樣的是須要用createSagaMiddleware工廠函數建立一個Saga middleware,而後鏈接到store上,以後便使用sagaMiddleware.run(mySaga) 運行Saga。promise

  saga.js
 1 import { call, put, takeEvery, takeLatest, take } from 'redux-saga/effects'
 2 import types from '../store/types'
 3 function* fetchUser(action) {
 4     try {
 5         const data = yield action.payload
 6         yield put({ type: `${action.type}_SUCCESS`, payload: data })
 7     } catch (e) {
 8         yield put({ type: `${action.type}_FAILD`, message: e.message })
 9     }
10 }
11 function* mySaga() {
12     yield takeLatest(types['GET_USER_INFO'], fetchUser)
13 }
14 export default mySaga

  由於saga是實現爲生成器函數( Generator functions),會yield對象到redux-saga middleware。被yield的對象都是一類指令,指令能夠被middleware解釋執行。當 middleware 取得一個 yield 後的 Promise,middleware 會暫停 Saga,直到 Promise 完成。在這裏就是會等到action中傳入的異步方法執行完畢後繼續執行下一步。而後調用put指令發起一個action
  action.js
1 import types from '../store/types';
2 import {
3     get_user_info
4 } from '../api/userInfo'
5 export const getUserInfo = (params) =>
6 ({
7     type: types.GET_USER_INFO,
8     payload: get_user_info()
9 })

  向saga提供action的type和異步請求的結果瀏覽器

  reducer.js緩存

 1 import types from '../store/types';
 2 import { reducerCreators } from '../util/index';
 3 const initState = {
 4 isLoading: false,
 5 userInfo: {},
 6 errorMsg: ''
 7 };
 8 export default reducerCreators(initState, {
 9     [`${types.GET_USER_INFO}_SUCCESS`]: (state, data) => {
10         return Object.assign({}, state, {
11             userInfo: data.data
12         })
13     }
14 })

  view.js

 1 import React, { Component } from 'react';
 2 import { connect } from 'react-redux';
 3 import { bindActionCreators } from 'redux'
 4 import { getUserInfo } from 'actions/userInfo';
 5 
 6 class UserInfo extends Component {
 7     render() {
 8     const { userInfo, isLoading, errMsg } = this.props.userInfo;
 9 
10         return (<div>
11             {
12                 isLoading ? '請求中' :
13                 (errMsg ? errMsg :
14                     <div>
15                         <p>用戶信息:</p>
16                         <p>用戶名:{userInfo.name}</p>
17                         <p>介紹:{userInfo.intro}</p>
18                     </div>
19                 )
20             }
21             <button onClick={() => this.props.getUserInfo()}>請求用戶信息</button>
22         </div>)
23     }
24 }
25 
26 const mapStateToProps = (state) => {
27     return {
28         userInfo: state.userInfo,
29     }
30 }
31 
32 const mapDispatchToProps = (dispatch) => {
33     return {
34         getUserInfo: bindActionCreators(getUserInfo, dispatch)
35     }
36 }
37 
38 export default connect(mapStateToProps, mapDispatchToProps)(UserInfo);

 

4、redux-thunk與redux-saga優缺點

Redux-thunk:
優勢:
  • 庫小量輕,代碼簡短
缺點:
  • thunk僅僅作了執行這個函數,並不在意函數主體內是什麼,也就是說thunk使得redux能夠接受函數做爲action,可是函數的內部能夠多種多樣
  • action的形式不統一
  • 異步操做分散,分散在各個action中 
 
 
Redux-saga
缺點:
  • 太複雜,學習成本較高
優勢: 
  • 集中處理了全部的異步操做,異步接口部分一目瞭然
  • action是普通對象,這跟redux同步的action如出一轍
  • 經過Effect,方便異步接口的測試
  • 經過worker 和watcher能夠實現非阻塞異步調用,而且同時能夠實現非阻塞調用下的事件監聽
  • 異步操做的流程是能夠控制的,能夠隨時取消相應的異步操做。 
 
(關於mobx的異步操做,主要使用了async\await和特有的runInAction,有興趣的能夠自行百度,很簡便的用法)
相關文章
相關標籤/搜索