redux middleWare 原理及异步实战
Redux 解决了 react 中出现的多交互、多数据源问题,但是如果有异步操作,或者要对操作进行拦截或者执行后回调就比较麻烦。于是我们需要 Redux 中间件。
一、手动增强 store.dispatch
我们知道 react-redux 的 connect 是一个高阶组件,它将组件包装之后拿到一个能够在组件中直接获取 context 的 state 的组件,并且用 dispatch 监听每一个 action:
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
...
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {} // 用来 dispatch 的时候获取 store 的 dispatch
...
render() {
return <WrappedComponent {...this.state.allProps}/>
}
}
return Connect;
}
如果要增强 dispatch,我们可以对其进行重构,直接改写 store 实例中的 dispatch:
let store = createStore(rootReducer);
let dispatch = store.dispatch//拿到dispatch
store.dispatch = function (action) {//对dispatch进行重构
console.log('旧状态',store.getState())//1.打印就状态
dispatch(action)//2.在执行之前的action
console.log('新状态',store.getState())//3.打印新状态
}
以上的代码增强了 dispatch 方法,使执行顺序变成了 action->log->reducer->log
。
二、使用 redux 的 applyMiddleware 方法增强 store.dispatch
redux 提供了类似后端 Express 的中间件概念,本质的目的是提供第三方插件的模式,自定义拦截 action -> reducer
的过程。变为 action -> middlewares -> reducer
。这种机制可以让我们改变数据流,实现如异步 action ,action 过滤,日志输出,异常报告等功能。
官方说明如下:使用中间件扩展增强 Redux store 上的
dispath
方法。因为中间件可能是异步的,所以这应该是定义在组合链
中存储增强器。
export default function applyMiddleware(...middlewares) {//[middleware1,middleware2] 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) } const chain = middlewares.map(middleware => middleware(middlewareAPI))//将middleware放入链式数组 dispatch = compose(...chain)(store.dispatch)//依次执行 <span class="hljs-built_in">return</span> {// 将增强过的dispatch返回 ...store, dispatch }
}
}
applyMiddleware 的使用方法,官方文档中给出 两种
用法:
const store = createStore(reducer, preloadedState, applyMiddleware(...))
const store = createStore(reducer, applyMiddleware(...))
第二个参数初始化 state 不是必传的,源码中的 createStore 方法对参数进行了处理
三、middleware 如何工作
export default function createStore(reducer, preloadedState, enhancer) { if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined }
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
...
}
在 createStore 方法中判断参数并相应替换,最后 createStore 代码执行返回的是一个 enhancer 函数嵌套调用方法,也就是:
const store = applyMiddleware(...)(createStore)(reducer,preloadedState)
嵌套函数分别传入的 createStore
和 reducer
,创建了store,并定义了 dispatch 方法,并组合成obj传给了 logger 函数,logger 中间件函数接受两个参数 dispatch getState
(获取状态 派发动作) 并返回一个新的参数 next
,形成了一个 dispatch 增强函数。小白我对于这个一长串的 return 理解成如下:
let logger1 = function({dispatch,getState}) {
store.dispatch = function(action){
console.log('旧状态1',getState())
next(action)
console.log('新状态1',getState())
}
}
这已经跟文章开头手动增强 store.dispatch
的函数十分相近了,主要区别在于 next
方法。
middleware 通过 next(action)
一层一层处理和传递 action 直到 redux 原生的 dispatch,这时 next 为客户端调用的 dispatch 方法,action 为方法传入的 actionType:{type:xxx,payload:xxx}
,代码要优雅,小白我理解了就要按照官方的来,正确的 middleWare 一样定义格式如下:
let logger = ({dispatch,getState}) => next => action =>{
console.log('旧状态1',getState())
next(action)//dispatch(action)
console.log('新状态1',getState())
}
中间件的实现和洋葱模型很像,先触发 logger 第一层,再触发 dispatch 事件,最后再从 logger 函数出来。
四、compose 实现链式调用
实现多个中间件先后调用的关键是 compose 函数
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg }
if (funcs.length === 1) {
return funcs[0]
}return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
源码很精简,理解有点复杂。其实是使用 reduce 不断将 最右
先调动函数的结果返回给后调用的函数,举个栗子:
function add1(str){ return str+1; } function add2(str){ return str+2; } function add3(str){ return str+3; }
let add = compose(add3,add2,add1);
let r = add("啊哈哈哈") // 啊哈哈哈123
在这段代码中,compose 函数执行顺序为 add1->add2->add3,并将 结果作为参数
传给下一个函数。
在 redux 中当新 dispatch 执行时, [f1, f2, ... , fx, ..., fn]
,从右到左依次执行。
dispatch = f1(f2(f3(store.dispatch))))
如图所示,从右至左执行 logger2、logger1。logger2 返回的代码作为参数传给 logger1 的 next 参数。按照图上 1->2(执行下一个 middleware)->3->4(触发 redux 原生的 dispatch 方法)->5->6 完成
五、异步操作
很多时候,我们需要异步操作。用户触发第一个 dispatch 事件的 action,需要发送第二个 action。或者根据返回的根据发送第二个处理请求。
解决异步操作的方法:
- redux 函数的参数是 dispatch 和 getState,把返回的 obj 改成返回一个异步函数。
- 异步操作结束之后,再发出一个 Action。
function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
这样子能理想得解决异步操作,而 store.dispatch 方法正常情况下,参数只能是对象,不能是函数。
这个时候可以引入 redux-thunk
Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
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,若不是,则继续传递 action 到下个 middleware。运用方法:
let store = createStore(
rootReducer,
applyMiddleware(thunk,logger1,logger2)
)
当 store.dispatch 运行到 thunk 中间件,发现返回的是一个 function,则执行返回的函数,并返回,重新派发 dispatch。因此使用 redux-thunk,改造 store.dispatch。可以实现异步方法,还有如 redux-promise redux-saga 也可以解决异步的问题。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: 防抖和节流原理分析
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论