博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redux源码浅析系列(四):`applyMiddleware`
阅读量:6164 次
发布时间:2019-06-21

本文共 3935 字,大约阅读时间需要 13 分钟。

前面主要介绍了createStorecombineReducers,compose的实现原理,下面,我们看一下 redux中最有意思的中间件部分applyMiddlewareapplyMiddleware代码很简洁,但是含义很广泛。我们来一起看一下:

首先,我们先来重温一下中间件的使用方法:

  • 调用中间件
    来看一下createStore的源码
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {    enhancer = preloadedState    preloadedState = undefined  }  //enhancer必须是一个高阶函数  if (typeof enhancer !== 'undefined') {    if (typeof enhancer !== 'function') {      throw new Error('Expected the enhancer to be a function.')    }    //增强器    return enhancer(createStore)(reducer, preloadedState)  }复制代码

enhancer是中间件,当createStore的第二个参数是function且没有第三个参数时,也可以直接在第二个参数设置中间件。那么中间件就有两种设置方式

createStore(reducer,initState,applyMiddleware(ThunkMiddleware));复制代码

或者

createStore(reducer,applyMiddleware(ThunkMiddleware));复制代码

熟悉了用法之后,我们来看一下applyMiddleware的实现方式:

export default function applyMiddleware(...middlewares) {  return (createStore) => (reducer, preloadedState, enhancer) => {    // 生成一个store     const store = createStore(reducer, preloadedState, enhancer)    let dispatch = store.dispatch    let chain = []    //简陋版的store,里面包含了`getState`和`dispatch`两个方法。    const middlewareAPI = {      getState: store.getState,      dispatch: (action) => dispatch(action)    }    //将middlewareAPI作为参数注入到每个middleware中去,执行middleware, 返回一个新的链。    //中间件函数(store)=>next=>action.    // chain  [next=>action,...];    //这个 next 其实store.dispatch.   而`action`就是`dispatch`的action    chain = middlewares.map(middleware => middleware(middlewareAPI))        //我们假设有三个中间件,fn1,fn2,fn3,那么下面代码等同于  dispatch=fn1(fn2(fn3(store.dispatch)));    //可以发现,中间件所组成的dispatch 其实就是一个执行过fn1,fn2,fn3的函数。    //所以,每个中间件在遇到不是自己处理范围之内的action的时候,会使用next(action),将其传递给下一个中间件。    dispatch = compose(...chain)(store.dispatch)    return {      ...store,      dispatch    }  }}复制代码

再来一段redux-thunk中间件的源码做为参考,一起来看一下

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

我们从中间件的代码开始入手,一点点剖析applyMiddleware

在中间件的代码中有这样一段代码:({dispatch,getState})=>next=>action=>{}; 结合上面applyMiddleware的源码来看呢,很容易发现({dispatch,getState})指的就是middlewareAPI,是一个简陋版的store,而action就是我们dispatchaction。那么这个next是什么呢?

在执行chain = middlewares.map(middleware => middleware(middlewareAPI))的时候,我们将 middlewareAPI作为参数传进去,对应着中间件代码中的({dispatch,getState})=>这一步。则执行完的chain则表示[(next)=>action=>{},...]

继续向下看, dispatch = compose(...chain)(store.dispatch) 这时候,可以发现,其实next参数其实就是原始的store.dispatch的这个dispatch

那么,applyMiddleware在多中间件的场景下,是如何工作的呢? 我们来看一下dispatch = compose(...chain)(store.dispatch)。 假设我们有fn1,fn2,fn3三个中间件,那么以上函数就可以拆解为 dispatch=fn1(fn2(fn3(store.dispatch)));

我们举个例子来看:

const a = next => () => {		console.log('a pre');		next(); //执行b(c(d()));		console.log('a after');	}	const b = next => () => {		console.log('b pre');		next(); // 执行c(d());		console.log('b after');	}	const c = next => () => {		console.log('c pre');		next(); //执行  d(); 		console.log('c after');	}	const d = () => {		console.log('Hello World');	}		function compose(...funcs) {		return funcs.reduce((a, b) => (...args) => a(b(...args)))	}	console.log(compose(a, b, c)(d)())复制代码

打开浏览器的console,可以发现执行结果为

a preb prec preHello Worldc afterb aftera after复制代码

通过compose函数,将compose(a,b,c)(d)()合成为a(b(c(d)))(); 则首先执行a(),然后再控制台上打印出a pre,然后执行next(),此时的next其实就是b(c(d)),然后执行b(),打印出b pre,随后在执行next(),这时的next表示的是c(d),然后执行c(),打印出c pre,随后在执行next(),这时的next表示的是d,然后执行d(),打印出helloWorld. 随后继续执行,打印出c after,然后是b after,a after

这个例子很好的解释了中间件的执行过程。我们的compose函数只是将fn1,fn2,fn3组成了fn1(fn2(fn3()))函数,不会立即运行最里面的函数,只有fn1执行了next之后,才会执行fn2,同样,只有fn2执行了next之后,fn3才会执行。以此类推。假如,我们在某个层级不执行next了,那么这个链就断了。

所以,我们在使用redux-logger这个中间件的时候,必须要把它放在middlewares的最右边,就是因为担心后面的某个中间件万一不执行next了,整个链就断了,那么即使他在前面执行了next,也是没有用的。

这也要求我们,在编写中间件的时候,都要执行一次next(action),因为只要每个中间件都常规的执行next(action),就能保证这个链不断,原始的dispatch就可以一直分发下去了。

转载于:https://juejin.im/post/5a9d193df265da239b40ebde

你可能感兴趣的文章
Java基础学习总结(4)——对象转型
查看>>
BZOJ3239Discrete Logging——BSGS
查看>>
SpringMVC权限管理
查看>>
spring 整合 redis 配置
查看>>
cacti分组发飞信模块开发
查看>>
浅析LUA中游戏脚本语言之魔兽世界
查看>>
飞翔的秘密
查看>>
Red Hat 安装源包出错 Package xxx.rpm is not signed
查看>>
编译安装mysql-5.6.16.tar.gz
查看>>
活在当下
查看>>
每天进步一点----- MediaPlayer
查看>>
PowerDesigner中CDM和PDM如何定义外键关系
查看>>
跨域-学习笔记
查看>>
the assignment of reading paper
查看>>
android apk 逆向中常用工具一览
查看>>
MyEclipse 报错 Errors running builder 'JavaScript Validator' on project......
查看>>
Skip List——跳表,一个高效的索引技术
查看>>
Yii2单元测试初探
查看>>
五、字典
查看>>
前端js之JavaScript
查看>>