Redux-thunk快速入門

Guohjia發表於2019-03-04

前言

最近剛剛完成了畢業答辯,我的畢設內容是基於React系列技術棧開發的一個類似Instagram的Web App,戳此看看。開發完後,我驚奇的發現:咦,之前就聽說有個叫做redux-thunk的東西,我怎麼沒用到?業務場景太簡單了?於是大概研究了下。。

概念的基本介紹

關於redux-thunk的基本介紹,也許你可以先看看stackoverflow上面的介紹。個人理解:redux-thunk改寫了dispatch API,使其具備接受一個函式作為引數的能力,從而達到middleware的效果,即在redux的dispatch action => reducer => store這個流程中,在action 被髮起之後,到達 reducer 之前的擴充套件點,加入相關操作,比如發生請求、log資訊等。

實際使用

以我本次畢設專案中,在redux流程中加入非同步請求為例,為動態點贊功能部分程式碼實現:

  • 本來我是這樣寫的
//from action.js
const LIKE = (id) => ({
     type: "LIKE",
     id:id
})

reqLike({id:id}).then(res =>{ dispatch(LIKE(id))})
複製程式碼

可以看到,我在請求以後的回撥函式中dispatch action去同步redux store中的狀態。

  • 加入redux-thunk之後我是這樣寫的:
//from action.js
const LIKE = (id) => {
    return function (dispatch,getState) {
        reqLike({id:id}).then(res =>{ 
            dispatch({
                type: "LIKE",
                id:id
            })
        })
    }
}

dispatch(LIKE(id))
複製程式碼

改變以後,從功能層面上來說,兩者並無差別,都可以滿足業務場景需求。但除此之外我們可以發現:

  • 1.dispatch接受的引數由一個PlainObject變為一個函式
  • 2.我們把請求的非同步操作從dispatch action這個redux流程外塞到的流程裡,這看起來將非同步操作內聚到這個流程中,無論是從邏輯上理解(這很middleware!)還是專案程式碼開發維護(區分非同步與同步狀態管理流程進行維護管理)上都是很大的改進
  • 3.如果專案中有多處需要實現點贊功能,我們可以節省很多冗餘程式碼,不用到處在dispatch外層套上reqLike(id).then(…)

原始碼解析

瞭解了redux-thunk的基本概念以及應用後,我們一起看看原始碼加深下理解吧,原始碼十分精巧。

首先看到redux原始碼中applyMiddleware的部分,我們將thunk作為引數傳入之後,直接返回了一個函式,這個函式作為enhancer傳入redux原始碼中的createStore函式中。

export default function applyMiddleware(...middlewares) {
  //這個返回函式就是enhancer
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
複製程式碼

在redux原始碼中的createStore函式中,enhancer被執行,傳入引數createStore,又緊接著執行其返回的函式,傳入reducer和preloadedState.

if (typeof enhancer !== `undefined`) {
    if (typeof enhancer !== `function`) {
      throw new Error(`Expected the enhancer to be a function.`)
    }
    
    return enhancer(createStore)(reducer, preloadedState)
}
複製程式碼

接下來,我們進入applyMiddleware和thunk的關鍵部分,上面applyMiddleware接受的最初的(…middlewares)引數其實就是thunk,thunk會被執行,並且傳入引數getState和dispatch.

//傳入到thunk的引數
const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
//在map中執行thunk
chain = middlewares.map(middleware =>middleware(middlewareAPI))
//重新改寫dispatch
dispatch = compose(...chain)(store.dispatch)
複製程式碼

那麼上面的chain是什麼呢,我們終於可以去看redux-thunk的原始碼了!

function createThunkMiddleware(extraArgument) {
  return function (_ref) {
    var dispatch = _ref.dispatch,
        getState = _ref.getState;
    //這裡返回的函式就是chain
    return function (next) {
    //這裡返回的函式就是改寫的dispatch
      return function (action) {
        if (typeof action === `function`) {
          return action(dispatch, getState, extraArgument);
        }

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

var thunk = createThunkMiddleware();
複製程式碼

從原始碼我們可以看出,chain就是以next作為形參的匿名函式,至於compose只是不斷傳遞每個函式的返回值給下一個執行函式,然後依次去執行它所有傳入的函式而已,它原始碼中的註釋說的很清楚:For example, compose(f, g, h) is identical to doing (…args) => f(g(h(…args))).

我們這裡的chain只是一個函式而已,所以很簡單,就是執行chain,並且傳入store.dispatch作為next就行。

接下來,進入最後一步,改寫了dispatch,最終變為:

function (action) {
    if (typeof action === `function`) {
      return action(dispatch, getState, extraArgument);
    }
    //next為之前傳入的store.dispatch,即改寫前的dispatch
    return next(action);
};
複製程式碼

如果傳入的引數是函式,則執行函式。否則還是跟之前一樣dispatch(PlainObject).

總結

redux-thunk實現了相關非同步流程內聚到redux的流程中,實現middleware的功能,也便於專案的開發與維護,避免冗餘程式碼。而實現的方式便是改寫redux中的dispatch API,使其可以除PlainObject外,接受一個函式作為引數。