標籤:
教程原始碼及目錄
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中介軟體