在上一周的学习中,咱们熟悉了如何经过redux去管理数据,而在这一节中,咱们将一块儿深刻到redux的知识中学习。html
咱们知道在一个简单的数据流场景中,点击一个button后,在回调中分发一个action,reducer收到action后就会更新state并通知view从新渲染,以下图所示
ios
可是若是须要打印每个action来调试,就得去改dispatch或者reducer实现,使其具有打印功能,那么该如何作?所以,须要中间件的加入。
编程
上图展现了应用middleware后的Redux处理事件的逻辑,每一个middleware均可以处理一个相对独立的事物,经过串联不一样的middleware实现变化多样的功能!json
Redux提供了一个applyMiddleware方法来加载middleware,它的源码是这样的:redux
import compose from './compose'; export default function applyMiddleware(...middlewares) { return (next) => (reducer, initalState) => { let store = next(reducer, initalState); let dispatch = store.dispatch; let chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; chain = middlewares.map( middleware => middleware(middlewareAPI)); dispatch = compose(...chain)(store.dispatch); return { ...store, dispatch }; } }
而后咱们再上一个logger middleware的源码实现:数组
export default store => next => action => { console.log('dispatch:', action); next(action); console.log('finish:', action); }
虽然看到“源码”的那两个字的时候,心里一万只草什么马奔过,可是一看到代码这么精简,这么优美,那就初读一下源码把。
而后
闭包
接下来就开始解读上面源码app
middleware是一个层层包裹的匿名函数,这实际上是函数式编程的currying(Currying就是把一个带有多个参数的函数拆分红一系列带部分参数的函数)。那么applyMiddleware会对logger这个middleware进行层层的调用,动态的将store和next参数赋值。异步
那么currying的middleware结构有什么好处呢?函数式编程
而且applyMiddleware的结构也是一个多层currying的函数,借助compose,applyMiddleware能够用来和其余插件增强createStore函数
经过以下方式建立一个普通的store
let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);
上述代码执行完后,applyMiddleware方法陆续得到了3个参数,第一个是middlewares数组[mid1, mid2, mid3,...],第二个是Redux原生的createStore方法,最后一个是reducer。而后咱们能够看到applyMiddleware利用createStore和reducer建立了一个store。而store的getState方法和dispatch方法又分别被直接和间接地赋值给middlewareAPI变量的store
const middleAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middle => middleware(middlewareAPI))
而后,每一个middleware带着middlewareAPI这个参数分别执行一遍,执行后,获得一个chain数组[f1, f2, ..., fx, ..., fn],它保存的对象是第二个箭头函数返回的匿名函数。由于是闭包,每一个匿名函数多能够访问相同的store,即middlewareAPI.
这一层只有一行代码,确是applyMiddleware精华所在。
dispatch = compose(...chain)(store.dispatch);
其中,compose是函数式编程中的组合,它将chain中的全部匿名函数[f1, f2, ..., fn]组装成一个新的函数,即新的dispatch。当新的dispatch执行的时候,[f1, f2, ...]会从右到左依次执行。Redux中compose的实现是这样的,固然实现的方式不惟一。
function compose(...funs) { return arg => funcs.reduceRight( (compose, f) => f(composed), arg) }
compose(...funcs)返回的是一个匿名函数,其中funcs就是chain数组。当调用reduceRight时,依次从funcs数组的右端取一个函数f(x)拿来执行,f(x)的参数composed就是前一次f(x+1)执行的结果,而第一次执行的f(n)n表明chain的长度,它的参数arg就是store.dispatch。
所以,当compose执行完后,咱们获得的dispatch是这样的:
假设n=3:
dispatch = f1(f2(f3(store.dispatch)));
这时调用dispatch,每个middleware就会依次执行了。
通过compose后,全部的middleware就算是已经串联起来了。
那么问题来了?
在分发store时,咱们有说到每一个middleware均可以访问store,也就是咱们说的经过middlewareAPI这个变量去拿到dispatch属性进行操做。那么若是在middleware中调用store.dispatch会如何?和调用next()有什么区别?
先上一波代码:
//next() const logger = store => next => action => { console.log('dispatch:', action); next(action); console.log('finish:', action ); } //store.dispatch(action); const logger = store => next => action { console.log('dispatch:', action); store.dispatch(action); console.log('finishL:', action); }
在分发store的时候,咱们有说过:midddleware中的store的dispatch经过匿名函数的方式和最终compose结束后的新dispatch保持一致,因此,在middleware中调用store.dispatch()和在其余任何地方调用其实效果是同样的。
而若是在middleware中调用next(),效果是进入下一个middleware中。
具体以下两个图1和图2所示:
如图1所示,正常状况下,当咱们去分发一个action时,middleware会经过next(action)一层层处理和传递action直到redux原生的dispatch。若是某个middleware中使用了store.dispatch(action)来分发action,就会发生如图2所示的状况。这就至关因而又从头开始了。
那么问题又来了,假如这个middleware一直简单粗暴地调用store.dispatch(action),就会造成一个无限循环了,那么store.dispatch(action)的用武之地到底在哪里呢?
假如咱们须要发送一个异步请求到服务端获取数据,成功后弹出个message,这里咱们一般会用到reduce-thunk这个插件。
const thunk = store => next => action => typeof action === 'function' ? action(store.dispatch, store.getState) : next(action)
代码很清晰,就是会先判断你dispatch过来的action是否是一个function,若是是则执行action,若是不是则传递到下一个middleware。所以,咱们能够这样设计action:
const getMessage = (dispatch, getState) => { const url = 'http://xxx.json'; Axios.get(url) .then( res => { dispatch({ type: 'SHOW_MESSAGE', message: res.data.data }) }) .catch( err => { dispatch({ type: 'ERR_GET', message: 'error' }) }) }
如上所示,只要在应用中去调用store.dispatch(getThenShow), redux-thunk这个middleware接收到后就会去执行getMessage方法。getMessage会先请求数据,根据请求结果去作相对应的分发action。这这里的dispatch就是经过redux-thunk这个middleware传递进来的。 总结:在middleware中使用dispatch的场景通常是接受到一个定向action,而这个action又并不但愿到达原生的分发action,每每用在异步请求的需求里面。
----------------做者的话:其实看了挺多遍,在脑海中构建整个middleware流程,再结合上周学习Redux时的Demo才渐渐的知其形,还须要多在实践中会其神!--------------