We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
为了对中间件有一个整体的认识,先从用法开始分析。调用中间件的代码如下:
源码 createStore.js#39
export default function createStore(reducer, preloadedState, enhancer) { if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } }
enhancer 是中间件,且第二个参数为 Function 且没有第三个参数时,可以转移到第二个参数,那么就有两种方式设置中间件:
enhancer
Function
const store = createStore(reducer, null, applyMiddleware(...))
const store = createStore(reducer, applyMiddleware(...))
再看 源码 中间件的传参:
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) ... }
就是为了得到 store,并通过 createStore 创建,上述两种方法因为在 createStore 函数内部传入了自身函数才得以实现 :
store
createStore
export default function createStore(reducer, preloadedState, enhancer) { ... if (typeof enhancer !== 'undefined') { return enhancer(createStore)(reducer, preloadedState) } ... }
上述代码可以看出,创建 store 的过程完全交给中间件了,因此开启了中间件第三种使用方式:
const store = applyMiddleware(...)(createStore)
大家对剖析 applyMiddleware 源码都非常感兴趣,因为它实现精简,但含义甚广,再重温其源码:
applyMiddleware
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) 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) return { ...store, dispatch } } }
假设大家都已了解 ES6 7 语法,懂得 compose 函数的含义,并且看过一些源码剖析了,我们才能把重点放在核心原理上:为什么中间件函数有三个传参 store => next => action ,第二个参数 next 为什么拥有神奇的作用?
compose
store => next => action
next
代码前几行创建了 store (如果第三个参数是中间件,就会出现中间件 store 包中间件 store 的情况,但效果是完全 打平 的), middlewareAPI 这个变量,其实就是精简的 store, 因为它提供了 getState 获取数据,dispatch 派发动作。
middlewareAPI
getState
dispatch
下一行,middlewares.map 将这个 store 作为参数执行了一遍中间件,所以中间件第一级参数 store 就是这么来的。
middlewares.map
下一步我们得到了 chain, 倒推来看,其中每个中间件只有 next => action 两级参数了。我们假设只有一个中间件 fn ,因此 compose 的效果是:
chain
next => action
fn
dispatch = fn(store.dispatch)
那么 next 参数也知道了,就是 store.dispatch 这个原始的 dispatch.
store.dispatch
代码的最后,返回了 dispatch ,我们一般会这么用:
store.dispatch(action)
等价于
fn(store.dispatch)(action)
第三个参数也来了,它就是用户自己传的 action.
action
我们展开代码来查看一个中间件的运行情况:
fn(middlewareAPI)(store.dispatch)(action)
对应 fn 的代码可能是:
export default store => next => action => { console.log('beforeState', store.getState()) next(action) console.log('nextState', store.getState()) }
当我们执行了 next(action) 后,相当于调用了原始 store dispatch 方法,并将 action 传入其中,可想而知,下一行输出的 state 已经是更新后的了。
next(action)
state
但是 next 仅仅是 store.dispatch, 为什么叫做 next 我们现在还看不出来。
详见 dispatch 后立刻修改 state:
function dispatch(action) { ... currentState = currentReducer(currentState, action) ... }
其中还有一段更新监听数组对象,以达到 dispatch 过程不受干扰(快照效果) 作为课后作业大家独立研究:主要思考这段代码的意图:https://github.com/reactjs/redux/blob/master/src/createStore.js#L63
我们假设有三个中间件 fn1 fn2 fn3, 从源码的这两句入手:
fn1
fn2
fn3
chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch)
第一行代码,我们得到了只剩 next => action 参数的 chain, 暂且叫做:
cfn1 cfn2 cfn3, 并且有如下对应关系
cfn1
cfn2
cfn3
cfnx = fnx(middlewareAPI)
第二行代码展开后是这样的:
dispatch = cfn1(cfn2(cfn3(store.dispatch)))
可以看到最后传入的中间件 fn3 最先执行。
为了便于后面理解,我先把上面代码的含义写出来:通过传入原始的 store.dispatch, 希望通过层层中间件的调用,最后产生一个新的 dispatch. 那么实际上,中间件所组成的 dispatch, 从函数角度看,就是被执行过一次的 cfn1 cfn2 cfn3 函数。
我们就算不理解新 dispatch 的含义,也可以从代码角度理解:只要执行了新的 dispatch , 中间件函数 cfnx 系列就要被执行一次,所以 cfnx 的函数本身就是中间件的 dispatch。
cfnx
对应 cfn3 的代码可能是:
export default next => action => { next(action) }
这就是这个中间件的 dispatch.
那么执行了 cfn3 后,也就是 dispatch 了之后,其内部可能没有返回值,我们叫做 ncfn3,大概如下:
ncfn3
export default action => {}
但其函数自身就是返回值 返回给了 cfn2 作为第一个参数,替代了 cnf3 参数 store.dispatch 的位置。
cnf3
我们再想想,store.dispatch 的返回值是什么?不就是 action => {} 这样的函数吗?这样,一个中间件的 dispatch 传递完成了。我们理解了多中间件 compose 后可以为什么可以组成一个新的 dispatch 了(其实单一中间件也一样,但因为步骤只有一步,让人会想到直接触发 store.dispatch 上,多中间件提炼了这个行为,上升到组合为新的 dispatch)。
action => {}
对于 cfn3 来说, next 就是 store.dispatch 。我们先不考虑它为什么是 next , 但执行它了就会直接执行 store.dispatch ,后面立马拿到修改后的数据不奇怪吧。
对于 cfn2 来说,next 就是 cfn3 执行后的返回值(执行后也还是个函数,内层并没有执行),我们分为两种情况:
这就是 redux-thunk 的核心思想,如果 action 是个 function ,就故意执行 action , 而不执行 next(action) , 等于让 store.dispatch 失效了!但其目的是明确的,因为会把 dispatch 返回给用户,让用户自己调用,正常使用是不会把流程停下来的。
redux-thunk
function
next(action) => store.dispatch(action)
redux-logger
logger
我在考虑这样会不会有很大的局限性,但后来发现,只要中间件常规情况执行了 next(action) 就能保证原始的 dispatch 可以被继续分发下去。只要每个中间件都按照这个套路来, next(action) 的效果就与 yield 类似。
yield
所以 next 并不是完全意义上的洋葱模型,只能说符合规范(默认都执行了 next(action))的中间件才符合洋葱模型。
koa 的洋葱模型可是有技术保证的,generator 可不会受到代码的影响,而 redux 中间件的洋葱模型,会因为某一层不执行 next(action) 而中断,而且从右开始直接切断。
generator
redux
理解了上面说的话,就很简单了,并不是 store.dispatch(action) 中断了原始 dispatch 的传递,而是你执行完以后不调用 next 函数中断了传递。
还是要画个图总结一下,在不想看文字的时候:
The text was updated successfully, but these errors were encountered:
No branches or pull requests
用法
为了对中间件有一个整体的认识,先从用法开始分析。调用中间件的代码如下:
源码 createStore.js#39
enhancer
是中间件,且第二个参数为Function
且没有第三个参数时,可以转移到第二个参数,那么就有两种方式设置中间件:再看 源码 中间件的传参:
就是为了得到
store
,并通过createStore
创建,上述两种方法因为在createStore
函数内部传入了自身函数才得以实现 :上述代码可以看出,创建 store 的过程完全交给中间件了,因此开启了中间件第三种使用方式:
applyMiddleware 源码解析
大家对剖析
applyMiddleware
源码都非常感兴趣,因为它实现精简,但含义甚广,再重温其源码:假设大家都已了解 ES6 7 语法,懂得
compose
函数的含义,并且看过一些源码剖析了,我们才能把重点放在核心原理上:为什么中间件函数有三个传参store => next => action
,第二个参数next
为什么拥有神奇的作用?store
代码前几行创建了
store
(如果第三个参数是中间件,就会出现中间件 store 包中间件 store 的情况,但效果是完全 打平 的),middlewareAPI
这个变量,其实就是精简的store
, 因为它提供了getState
获取数据,dispatch
派发动作。下一行,
middlewares.map
将这个store
作为参数执行了一遍中间件,所以中间件第一级参数store
就是这么来的。next
下一步我们得到了
chain
, 倒推来看,其中每个中间件只有next => action
两级参数了。我们假设只有一个中间件fn
,因此compose
的效果是:那么
next
参数也知道了,就是store.dispatch
这个原始的dispatch
.action
代码的最后,返回了
dispatch
,我们一般会这么用:等价于
第三个参数也来了,它就是用户自己传的
action
.单一中间件的场景
我们展开代码来查看一个中间件的运行情况:
对应
fn
的代码可能是:当我们执行了
next(action)
后,相当于调用了原始store
dispatch
方法,并将action
传入其中,可想而知,下一行输出的state
已经是更新后的了。但是
next
仅仅是store.dispatch
, 为什么叫做next
我们现在还看不出来。详见 dispatch 后立刻修改 state:
其中还有一段更新监听数组对象,以达到
dispatch
过程不受干扰(快照效果) 作为课后作业大家独立研究:主要思考这段代码的意图:https://github.com/reactjs/redux/blob/master/src/createStore.js#L63多中间件的场景
我们假设有三个中间件
fn1
fn2
fn3
, 从源码的这两句入手:第一行代码,我们得到了只剩
next => action
参数的chain
, 暂且叫做:cfn1
cfn2
cfn3
, 并且有如下对应关系第二行代码展开后是这样的:
可以看到最后传入的中间件
fn3
最先执行。为了便于后面理解,我先把上面代码的含义写出来:通过传入原始的
store.dispatch
, 希望通过层层中间件的调用,最后产生一个新的dispatch
. 那么实际上,中间件所组成的dispatch
, 从函数角度看,就是被执行过一次的cfn1
cfn2
cfn3
函数。我们就算不理解新
dispatch
的含义,也可以从代码角度理解:只要执行了新的dispatch
, 中间件函数cfnx
系列就要被执行一次,所以cfnx
的函数本身就是中间件的dispatch
。对应
cfn3
的代码可能是:这就是这个中间件的
dispatch
.那么执行了
cfn3
后,也就是dispatch
了之后,其内部可能没有返回值,我们叫做ncfn3
,大概如下:但其函数自身就是返回值 返回给了
cfn2
作为第一个参数,替代了cnf3
参数store.dispatch
的位置。我们再想想,
store.dispatch
的返回值是什么?不就是action => {}
这样的函数吗?这样,一个中间件的dispatch
传递完成了。我们理解了多中间件compose
后可以为什么可以组成一个新的dispatch
了(其实单一中间件也一样,但因为步骤只有一步,让人会想到直接触发store.dispatch
上,多中间件提炼了这个行为,上升到组合为新的dispatch
)。再解释 next 的含义
为什么我们在中间件中执行
next(action)
,下一步就能拿到修改过的store
?对于
cfn3
来说,next
就是store.dispatch
。我们先不考虑它为什么是next
, 但执行它了就会直接执行store.dispatch
,后面立马拿到修改后的数据不奇怪吧。对于
cfn2
来说,next
就是cfn3
执行后的返回值(执行后也还是个函数,内层并没有执行),我们分为两种情况:cfn3
没有执行next(action)
,那cfn1
cfn2
都没法执行store.dispatch
,因为原始的dispatch
没有传递下去,你会发现dispatch
函数被中间件搞失效了(所以中间件还可以捣乱)。为了防止中间件瞎捣乱,在中间件正常的情况请执行next(action)
.cfn3
执行了next(action)
, 那cfn2
什么时候执行next(action)
,cfn3
就什么时候执行next(action) => store.dispatch(action)
, 所以这一步的next
效果与cfn3
相同,继续传递下去也同理。我看了下redux-logger
的文档,果然央求用户把自己放在最后一个,其原因是害怕最右边的中间件『捣乱』,不执行next(action)
, 那logger
再执行next(action)
也无法真正触发dispatch
.所以
next
并不是完全意义上的洋葱模型,只能说符合规范(默认都执行了next(action)
)的中间件才符合洋葱模型。为什么在中间件直接
store.dispatch(action)
,传递就会中断?理解了上面说的话,就很简单了,并不是
store.dispatch(action)
中断了原始dispatch
的传递,而是你执行完以后不调用next
函数中断了传递。总结
还是要画个图总结一下,在不想看文字的时候:
The text was updated successfully, but these errors were encountered: