react+redux教程(七)自訂redux中介軟體

來源:互聯網
上載者:User

標籤:

教程原始碼及目錄

https://github.com/lewis617/myReact

今天,我們要講解的是自訂redux中介軟體這個知識點。本節內容非常抽象,特別是中介軟體的定義原理,那多層的函數嵌套和串聯,需要極強邏輯思維能力才能完全消化吸收。不過我會多羅嗦幾句,所以不用擔心。

例子

例子是官方的例子real-world,做的是一個擷取github使用者、倉庫的程式。

本例子原始碼:

https://github.com/lewis617/myReact/tree/master/redux-examples/real-world

redux中介軟體就是個嵌套函數

redux中間一共嵌套了三層函數,分別傳遞了store、next、action這三個參數。返回的是next。

 

為什麼要嵌套函數?為何不在一層函數中傳遞三個參數,而要在一層函數中傳遞一個參數,一共傳遞三層?因為中介軟體是要多個首尾相連的,對next進行一層層的“加工”,所以next必須獨立一層。那麼store和action呢?store的話,我們要在中介軟體頂層放上store,因為我們要用store的dispatch和getState兩個方法。action的話,是因為我們封裝了這麼多層,其實就是為了作出更進階的dispatch方法,但是在進階也是dispatch,是dispatch,就得接受action這個參數。

看我們例子中的代碼:

middleware/api.js

// A Redux middleware that interprets actions with CALL_API info specified.// Performs the call and promises when such actions are dispatched.export default store => next => action => {  const callAPI = action[CALL_API]  if (typeof callAPI === ‘undefined‘) {    return next(action)  }  let { endpoint } = callAPI  const { schema, types } = callAPI  if (typeof endpoint === ‘function‘) {    endpoint = endpoint(store.getState())  }  if (typeof endpoint !== ‘string‘) {    throw new Error(‘Specify a string endpoint URL.‘)  }  if (!schema) {    throw new Error(‘Specify one of the exported Schemas.‘)  }  if (!Array.isArray(types) || types.length !== 3) {    throw new Error(‘Expected an array of three action types.‘)  }  if (!types.every(type => typeof type === ‘string‘)) {    throw new Error(‘Expected action types to be strings.‘)  }  function actionWith(data) {    const finalAction = Object.assign({}, action, data)    delete finalAction[CALL_API]    return finalAction  }  const [ requestType, successType, failureType ] = types  next(actionWith({ type: requestType }))  return callApi(endpoint, schema).then(    response => next(actionWith({      response,      type: successType    })),    error => next(actionWith({      type: failureType,      error: error.message || ‘Something bad happened‘    }))  )}

例子中,我們用store => next => action =>實現了三層函數嵌套。箭頭文法很好的代替了醜陋的function嵌套方法。最後指向的那個{}裡面,我們就可以寫關於dispatch的裝飾了,不過記得返回next,給下一個中介軟體使用。

中介軟體的執行

嵌套函數也是函數,是函數就要運行。我們知道,js裡面,我們用()來執行一個函數。那麼三層嵌套函數我們要怎麼執行呢?寫三個()唄!aaa()()()就可以了。aaa()返回了一個函數,aaa()()又返回一個函數,aaa()()()終於執行完成。

我們來看下,我們編寫的中介軟體是怎麼啟動並執行。首先中介軟體的使用是在configStore裡面的applyMiddleware裡面寫的。

applyMiddleware(thunk, api)

我們看下redux的原始碼:

node_modules/redux/src/applyMiddleware.js

export default function applyMiddleware(...middlewares) {  return (createStore) => (reducer, initialState, enhancer) => {    var store = createStore(reducer, initialState, enhancer)    var dispatch = store.dispatch    var chain = []    var middlewareAPI = {      getState: store.getState,      dispatch: (action) => dispatch(action)    }    chain = middlewares.map(middleware => middleware(middlewareAPI))    dispatch = compose(...chain)(store.dispatch)    return {      ...store,      dispatch    }  }}

好短好開心!不過看起來還是挺複雜的,不用擔心,不就是個三層嵌套函數嘛,我們先找最外層的執行代碼:

chain = middlewares.map(middleware => middleware(middlewareAPI))

middlewares使用展開文法,將我們寫進去的中介軟體,產生一個中介軟體數組。遍曆每個中介軟體,在中介軟體最外層執行第一次,也就是參數為store那一次。我們發現store是middlewareAPI。

var middlewareAPI = {      getState: store.getState,      dispatch: (action) => dispatch(action)    }

是個對象,包含了我們需要的兩個方法,這就夠了。

好了,我們開始尋找第二層函數的執行代碼:

dispatch = compose(...chain)(store.dispatch)

這是什麼鬼?compose是種函數嵌套的寫法,原始碼清單就不展示了,我們知道它可以幫我們將嵌套的函數,寫成逗號隔開的樣子就可以了。有點類似Promise解決回調地獄的做法。都是將嵌套寫成平行的樣子。

chain就是我們的第二層函數,...就是展開文法,用逗號隔開放進compose參數裡。後面那個(store.dispatch)就是入口參數。是個未經任何加工的dispatch,這個可憐的傢伙進去後,將被我們的中介軟體層層加工,經曆風雨,變成更加牛逼的dispatch。

第三層函數的執行代碼在哪?不在這裡面,因為到了第三層函數,就是新的dispatch了,我們要在組件裡面使用它,所以第三層函數的執行在組件中。參數是什嗎?當然是可愛的action啊!

中介軟體的串連

我們知道,中介軟體是可以首尾相連使用的,那麼我們如何?首尾相連?答案就在next(),寫過express中介軟體的同學對它一定不陌生。那麼redux中介軟體的next()是個什麼鬼?也是個dispatch。

我們寫中介軟體時候,一定要返回next(),這個next就是當前中介軟體對dispatch的加工,加工後返回給後面的中介軟體繼續加工。這也是為什麼中介軟體的順序有講究的原因。

解讀例子中的中介軟體的商務程序

中介軟體的原理講完了,我們來走一遍例子中的中介軟體商務程序吧!

擷取指定action

我們在定義action的時候,在fetchUser等函數中返回了這些我們需要的東西:

actions/index.js

import { CALL_API, Schemas } from ‘../middleware/api‘export const USER_REQUEST = ‘USER_REQUEST‘export const USER_SUCCESS = ‘USER_SUCCESS‘export const USER_FAILURE = ‘USER_FAILURE‘// Fetches a single user from Github API.// Relies on the custom API middleware defined in ../middleware/api.js.function fetchUser(login) {  return {    [CALL_API]: {      types: [ USER_REQUEST, USER_SUCCESS, USER_FAILURE ],      endpoint: `users/${login}`,      schema: Schemas.USER    }  }}// Fetches a single user from Github API unless it is cached.// Relies on Redux Thunk middleware.export function loadUser(login, requiredFields = []) {  return (dispatch, getState) => {    const user = getState().entities.users[login]    if (user && requiredFields.every(key => user.hasOwnProperty(key))) {      return null    }    return dispatch(fetchUser(login))  }}

我們也就是指定這些action來進行“封裝的”。除了fetchUser,還有其他的,不列出代碼清單了。

我們添加console.log來查看action的真正樣子:

console.log(‘當前執行的action:‘,action);  const callAPI = action[CALL_API]

在執行initRoutes的action時候,我們得到了一個包含Symbol()的action對象,這就是我們想要的action。

Symbol()

Symbol()是什嗎?就是CALL_API。

middleware/api.js

// Action key that carries API call info interpreted by this Redux middleware.export const CALL_API = Symbol(‘Call API‘)

為什麼要用symbol,為了不衝突,symbol是個唯一的不變的標識,可以 用於對象的key。為了避免衝突而生,這裡也可以用字串來代替,但是不好,因為字串很容易命名衝突,你每次都要想個奇葩的長名字,所以還是用 symbol吧。再羅嗦一句,我們寫object.assign的時候,第一個參數設為{},第二個參數設為源,第三個參數設為拓展屬性的寫法,就是為了 相容包含symbol的對象。

消毒

擷取到指定的action後,我們要做一系列的異常處理:

middleware/api.js

 

if (typeof endpoint === ‘function‘) {    endpoint = endpoint(store.getState())  }  if (typeof endpoint !== ‘string‘) {    throw new Error(‘Specify a string endpoint URL.‘)  }  if (!schema) {    throw new Error(‘Specify one of the exported Schemas.‘)  }  if (!Array.isArray(types) || types.length !== 3) {    throw new Error(‘Expected an array of three action types.‘)  }  if (!types.every(type => typeof type === ‘string‘)) {    throw new Error(‘Expected action types to be strings.‘)  }

 

比較簡單,不羅嗦了。

執行請求action

擷取完action,也進行過消毒了,可以開始ajax請求了,首先發一個請求action

function actionWith(data) {    const finalAction = Object.assign({}, action, data)    delete finalAction[CALL_API]    return finalAction  }  const [ requestType, successType, failureType ] = types  next(actionWith({ type: requestType }))
執行ajax請求,結束後發出成功或者失敗action
return callApi(endpoint, schema).then(    response => next(actionWith({      response,      type: successType    })),    error => next(actionWith({      type: failureType,      error: error.message || ‘Something bad happened‘    }))  )
圖解流程

本來一個action,經過中介軟體的加工後,變成了一系列的流程。

 

react+redux教程(七)自訂redux中介軟體

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.