因为一直用业界封装好的如redux-logger、redux-thunk
此类的中间件,并无深刻去了解过redux
中间件的实现方式。正好前些时间有个需求须要对action
执行时作一些封装,因而借此了解了下Redux Middleware
的原理。
首先简单提下什么是中间件,该部分与下文关系不大,能够跳过。来看眼这个经典的图。react
不难发现:redux
middleware
时,在dispatch(action)
时会执行rootReducer
,并根据action
的type
更新返回相应的state
。middleware
时,简言之,middleware
会将咱们当前的action
作相应的处理,随后再交付rootReducer
执行。 好比现有一个action
以下:segmentfault
function getData() { return { api: '/cgi/getData', type: [GET_DATA, GET_DATA_SUCCESS, GET_DATA_FAIL] } }
咱们但愿执行该action
时能够发起相应请求,而且根据请求结果由定义的type
匹配到相应的reducer
,那么能够自定义方法处理该action
,所以该方法封装成中间件以前多是这样的:api
async function dispatchPre(action, dispatch) { const api = action.api; const [ fetching_type, success_type, fail_type] = action.type; // 拉取数据 const res = await request(api); // 拉取时状态 dispatch({type: fetching_type}); // 成功时状态 if (res.success) { dispatch({type: success_type, data: res.data}); console.log('GET_SUCCESS'); } // 失败时状态 if (res.fail) { dispatch({type: fail_type}); console.log('GET_FAIL'); }; } // 调用: dispatchPre(action(), dispatch)
那如何封装成中间件,让咱们在能够直接在dispatch(action)
时就作到这样呢?可能会首先想到改变dispatch
指向数组
// 储存原来的dispatch const dispatch = store.dispatch; // 改变dispatch指向 store.dispatch = dispatchPre; // 重命名 const next = dispatch;
截止到这咱们已经了解了中间件的基本原理了~闭包
了解了基本原理能有助于咱们更快地读懂middleware
的源码。
业务中,通常咱们会这样添加中间件并使用。app
createStore(rootReducer, applyMiddleware.apply(null, [...middlewares]))
接下来咱们能够重点关注这两个函数createStore
、applyMiddleware
async
// 摘至createStore export function createStore(reducer, rootState, enhance) { ... if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } /* 若使用中间件,这里 enhancer 即为 applyMiddleware() 如有enhance,直接返回一个加强的createStore方法,能够类比成react的高阶函数 */ return enhancer(createStore)(reducer, preloadedState) } ... }
再看看applyMiddleware
作了什么,applyMiddleware
函数很是简单,就十来行代码,这里将其完整复制出来。函数
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } // 一、将store对象的基本方法传递给中间件并依次调用中间件 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 二、改变dispatch指向,并将最初的dispatch传递给compose dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
根据源码,咱们能够将其主要功能按步骤划分以下:源码分析
一、依次执行middleware
。
将middleware
执行后返回的函数合并到一个chain
数组,这里咱们有必要看看标准middleware
的定义格式,以下
export default store => next => action => {} // 即 function (store) { return function(next) { return function (action) { return {} } } }
那么此时合并的chain
结构以下
[ ..., function(next) { return function (action) { return {} } } ]
二、改变dispatch
指向。
想必你也注意到了compose
函数,compose
函数以下:[...chain].reduce((a, b) => (...args) => a(b(...args)))
实际就是一个柯里化函数,即将全部的middleware
合并成一个middleware
,并在最后一个middleware
中传入当前的dispatch
。
*compose
可能会看得有点蒙,不理解柯里化函数的同窗能够跳到一个例子读懂compose先了解下。
// 假设chain以下: chain = [ a: next => action => { console.log('第1层中间件') return next(action) } b: next => action => { console.log('第2层中间件') return next(action) } c: next => action => { console.log('根dispatch') return next(action) } ]
调用compose(...chain)(store.dispatch)
后返回a(b(c(dispatch)))
。
能够发现已经将全部middleware
串联起来了,并同时修改了dispatch
的指向。
最后看一下这时候compose执行返回,以下
dispatch = a(b(c(dispatch))) // 调用dispatch(action) // 执行循序 /* 1. 调用 a(b(c(dispatch)))(action) __print__: 第1层中间件 2. 返回 a: next(action) 即b(c(dispatch))(action) 3. 调用 b(c(dispatch))(action) __print__: 第2层中间件 4. 返回 b: next(action) 即c(dispatch)(action) 5. 调用 c(dispatch)(action) __print__: 根dispatch 6. 返回 c: next(action) 即dispatch(action) 7. 调用 dispatch(action) */
上文提到compose是个柯里化函数,能够当作是将全部函数合并成一个函数并返回的函数。
例如先定义3个方法
function A(x){ return x + 'a' } function B(y){ return y + 'b' } function C(){ return 'c' } var d = [...A, b, C].reduce((a, b) => (d) => {console.log(d, a, b); a(b(d))}) d // 打印d // f (d) { console.log(d, a, b); return a(b(d)) } d('d') // 调用d /* * d * f(d) { console.log(d, a, b); return a(b(d)) } * f C() { return 'c' } */ /* * c * f A(x) { return x + 'a' } * f B(y) { return y + 'b' } */
不难发现,使用闭包,在调用d
的时候,将a
、b
函数储存在了内存中,调用时会依次将数组从右至左的函数返回作为参数传递给下一个函数使用