超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

applyMiddleware.js 源码阅读笔记

applyMiddleware 允许使用中间件middleware增强Redex的store的 dispatch 方法,这些中间件通过组合的方式构成一条中间件的链,通过这种方式,每个中间件无需关注其他中间件的操作。

接口

applyMiddleware(…middlewares)

每个中间件 middleware 的方法签名要求为 ({ getState, dispatch }) => next => action

applyMiddleware 的整体结构

函数的源代码非常简洁只有不到20行的代码,函数的整体结构如下:

function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch

...// 修改dispatch

return {
...store,
dispatch
}
}
}

代码结构看起来很是复杂,实际上应用了函数式编程的柯里化技术,实际上柯里化是把一个接收多个参数的函数转换成接收一个参数的技巧,为了便于理解,对函数去柯里化如下:

function applyMiddleware(...middlewares, createStore, reducer, preloadedState, enhancer) {
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch

...// 修改dispatch

return {
...store,
dispatch
}
}

这下很容易看出,applyMiddleware 实质是使用 createStore 创建 store ,然后修改增强 storedispatch 函数,store 的其他内容保留。

返回值这里使用了 es6 的新特性,可以翻译为:

return Object.assign({}, store, {dispatch: dispatch});

返回一个新对象,将 store 的所有属性复制进去,并使用新的 dispatch 函数替代 store 原有的 dispatch 函数。

注意到 applyMiddleware 的返回值为 (createStore) => (reducer, preloadedState, enhancer) => newStore, 如果阅读过 createStore 的源代码就会发现有这么几行:

function createStore(reducer, preloadedState, enhancer) {
...
if (typeof enhancer !== 'undefined') {
//如果传入了store enhancer即在调用了函数的时候传入了 applyMiddleware
//那么此时创建的 store 的 dispatch 函数即是加了中间件“特效”的
return enhancer(createStore)(reducer, preloadedState)
}
...
}

如果传递了 enhancer参数,那么就会使用 enhancer 创建 store, 而 enhancer 实际上就和 applyMiddleware 的返回值几乎一样。正是因为 applyMiddleware 采用了柯里化,才保证了可以调用 applyMiddle(middleware1, middleware2, ...middlewares) 作为 createStore 的 enhancer 参数。

综上,使用中间件函数创建 store 有两种方式:

// 1.使用 applyMiddle:
applyMiddle(middleware1, middleware2, ...middlewares)(createStore)(reducer, preloadedState, enhancer);

//2.使用 createStore:
createStore(
reducer,
preloadedState,
applyMiddlware(middleware1, middleware2, ...middlewares)
);

唯一的区别在于,使用 applyMiddle 还可以再次应用其他的 store enhancer 中间件。

dispatch 的增强

如果使用过其他的中间件结构比如nodejs的可以知道,中间件实际上是把 action-->reducer 变成: action -> middleware1 -> middleware2 -> ... -> reducer,继续阅读代码:

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)

首先定义了中间件的需要传递给中间件的接口 getState 和 dispatch, 这也定义了中间件的签名必定是 ({ dispatch, getState }) => next => action => {//do sth},这里有个细节,middlewareAPI 为什么不写成:dispatch: dispatch 而是写成: dispatch: (action) => dispatch(action) ,这个细节将在后面详细说明。

然后继续看代码,可以把chain的存储的内容转换为:

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

//等于:
chain = [
next => action => {}, //包含getState、dispatch函数的闭包,假定此函数为f1
next => action => {}, //包含getState、dispatch函数的闭包,假定此函数为f2
...
next => action => {}, //包含getState、dispatch函数的闭包,假定此函数为fn
]

为了后面描述的方便,这里为 chain 的成员分别命名 f1,f2, … fn 接着看 compose

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

compose 将函数从右向左组合:

compose([f1, f2, ..., fn])
//等价于:
(...args) => f1(f2(...(fn(...args))))

返回继续阅读 applyMiddleware 并把转换后的 compose 代入:

dispatch = compose(...chain)(store.dispatch)
//等价于:
dispatch = f1(f2(...(fn(store.dispatch))))

把这里的 f1, f2, ..., fn 分别替换回去:

dispatch = f1(f2(action => {
//middleware n pre do sth
//...

store.dispatch(action);

//middleware n after do sth
}));
...

dispatch = action => {
//middleware 1 pre do sth
//middleware 2 pre do sth
//...
//middleware n pre do sth

store.dispatch(action);

//middleware n after do sth
//...
//middleware 2 after do sth
//middleware 1 after do sth
}

其中 "pre do sth" 指的是中间件函数定义中的 next(action) 前面的代码,在 action 被处理前调用,而 "after do sth" 指的是中间件调用 next(action) 后的代码,在 action 被处理后调用,而 next(action) 实际上也是中间件将控制权移交下一个中间件,也即调用 applyMiddleware(middleware1, middleware2, ... middlewaren) 的执行过程为:middleware1 pre -> middleware2 pre -> ... -> middlewaren pre -> store.dispatch -> middlewaren ->... -> middleware2 -> middleware1函数。

注意: 中间件并不总是包含 "pre do sth""after do sth" 结构的,此外这里仅讨论只有同步操作的中间件,异步的情况后面讨论。

Middleware 中间件

经过上面分析,可以知道中间件的基本结构如下:

const Middleware = ({dispatch, action}) => next => action => {
//do sth
next(action);
//do sth
}

比如,可以编写日志中间件:

const logMiddleware = ({dispatch, action}) => next => action => {
console.log(new Date() + ' action: ');
console.log(action);
next(action);
}

中间件的函数签名实际上是有 dispatch 的,这个 dispatchnext 都是接收 action 作为参数的,他们有什么区别呢?

前面已经说过,nextaction 控制权移交下一个组件,其实质是被下一个中间件增强过的 “store.dispatch”。而回过头来再看一下 applyMiddleware 的源代码,可以发现中间件函数拿到的 dispatch实际是 :dispatch: (action) => dispatch(action) 传递给每个 middleware 的 dispatch 被一层匿名函数包裹,这样最终导致传递给每个中间件的 dispatch 实际上是组合了所有的中间件的dispatch,而不是 “store.dispatch” 这个初始的旧的 “dispatch”

而在中间件可以调用 next(action)dispatch(action) 的区别可以看下图:

dispatch(action) 和 next(action) 的区别

使用 next(action) 效果如图左,从外层逐层处理action,并将处理过的action传递给下一层,处理完毕后再逐层返回,而使用 dispatch(action) 效果如图右,调用以后重新返回到外层,相当于再次从头开始应用中间件,这种情况通常用于场景是中断当前流程,将控制权移交其他部分,待其他内容处理完成再跳回正常流程,比如异步请求场景。

下面以 redux-thunk 为例说一下这种情况,首先看一下源代码:

function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}

return next(action);
};
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

redux-thunk 判断 action 是否为函数,如果是,将控制权移交给 action 处理,把 dispatchgetState传递给 action ,待 action 处理完毕调用 dispatch(action) 此时 action 为正常的 action对象,回到正常中间件流程,然后redux-thunk 执行 next(action) 进入下一个中间件。

而这个 action 基本结构如下:

//action 定义
function success(data) {
return {
type: 'SUCCESS',
data
}
}

function error(err) {
return {
type: 'ERROR',
err
}
}

// 异步action定义
// 和同步 action 不同 返回函数接收 dispatch 做为参数
function asyncFetch(params) {
return function (dispatch, getState) {
return fetch(params)
.then(data => dispatch(success(data)
.catch(err => dispatch(error(err))
}
}

//调用:
dispatch(asyncFetch({...} }))

如上示例,asyncFetch({}) 返回值不是对象而是函数,thunk 中间件拿到这个 “函数action” 后,把控制权移交给这个函数,这个函数执行 fetch 异步获取数据,获取数据成功后通过 dispatch(successAction) 再次返回 thunk中间件 ,此时 successAction 是对象,因此执行 next(action) 进入下一个中间件处理,异常流程与之类似不再说明。

注意: 中间件是根据声明顺序从左到右调用的,所以如果使用到类似于 thunk 中间件的异步类中间件,一般要在applyMiddleware 参数的最前面声明,以免其他中间件被执行多次。

源码注释

/**
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
*
* See `redux-thunk` package as an example of the Redux middleware.
*
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
*
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
*
* @param {...Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
* 从返回值可以看出:
* 除了通过Redux.createStore(reducer, preloadedState, applyMiddlware(...middlewares))创建store
* 还可通过applyMiddlware(...middlewares)(createStore)(reducer, preloadedState, enhancer)创建store
*/
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
//内部使用原始的createStore创建store,以保证不影响store的其他接口
var store = createStore(reducer, preloadedState, enhancer)
//保留原始的没有被中间件修改过的dispatch
var dispatch = store.dispatch
var chain = []

var middlewareAPI = {
getState: store.getState,
//使用匿名函数保证传递给每个 middleware 的 dispatch 函数都是
//compose(...chain)(store.dispatch) 这个应用了全部中间件的增强过的最新的 dispatch
dispatch: (action) => dispatch(action)
}
//以redux为例:
//const thunk = ({ dispatch, getState }) => next => action => {
// if (typeof action === 'function') {
// return action(dispatch, getState, extraArgument);
// }
// return next(action);
//};
//middleware => middleware(middlewareAPI)对应的实际上是
//thunk => thunk(middlewareAPI)
//即chain的每一个元素的结构类似于:
//next => action => {
// ...
// if (typeof action === 'function') {
// return action(dispatch, getState, extraArgument);
// }
// 此时,next 实际上就是 store.dispatch,即被后一个middleware增强过的dispatch
// 又或者说 next 将 action 的控制权移交下一个中间件处理更为恰当
// return next(action);
//};
chain = middlewares.map(middleware => middleware(middlewareAPI))

//compose的实现简单来说如下:
//function compose(...funcs) {
// if (funcs.length === 1) {
// return funcs[0]
// }
// const last = funcs[funcs.length - 1]
// const rest = funcs.slice(0, -1)
// return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
// }
//简单来说compose([chain1, chain2, chain3])(store.dispatch)
//类似于:chain1(chain2(chain3(store.dispatch)))
//即传递给每个chain的next从参数实际上是上一个chain包装过的增强的dispatch
dispatch = compose(...chain)(store.dispatch)

//返回一个新的对象,拥有store的所有接口
//并且store的其他接口如getState、subscribe保持不变
//仅仅使用增强后dispatch函数的覆盖了store的原始的dispatch函数
return {
...store,
dispatch
}
}
}

相关

Redux 源码阅读笔记:

参考

本文阅读代码版本 3.5.2