网上的Redux中间件原理解释多有疏漏,譬如我在多篇blog上看到Redux中间件解释以及Redux中间件深入浅出(译文)前文解释的时候提到了chain,这其实很不准确,因为在applyMiddleWare内部不是按照Chainable call的逻辑实现的,很容易让人误会为middleware1.middleware2...这种错误的逻辑,下文会根据源码进行更正。后者,虽然调用逻辑将的大致对了,但是关于curry function不恰当的引入真的是大大的误导,因为按照curry的逻辑,调用顺序和实际源码中的完全是两个不同方向。
上面提到的缘起就是我更正的点和踩过的坑。关于middleware内部实现的fn组合和调用顺序的阐述,我凑巧今天早上看到一篇知乎专栏文章Redux middleware 详解,大家可以看,这位兄弟解释的过程和我debug得到的结果是一致的,我也不再赘述。下面,讲一讲,很怪的一个约定,为什么middleware都得习惯性最后
return next(action)
以及为何applyMiddleWare内部必须
dispatch = compose(...chain)(store.dispatch)
这样才能连接action 和 reducer
Q1
针对末尾的return,我只能说这真的只是习惯,因为,你可以在官方的例子real-world中修改api.js和thunk内部的实现,去掉最后的return.这是可以的,为什么呢?原因和Q2有重要关系。因为在Q2的最后enhancer就是通过store.dispatch讲前面处理过的action分发给了reducer。但是,注意这里有一个细节差别,那就是这种处理不是filter chain模式的逐步修改action得到的,而是将action带有的副作用在中间件内部实现消化。换言之,每一个middleware都不是后一个或者前一个的return值提供者。
仅仅是每个人都处理自己感兴趣的部分,但是都不能修改action,同时根据需要调用next(action)将整个chain继续下去,当然,如果你觉得在某一步出错不用后续处理了,就可以不掉用。
最后,说一点,所有的dispatch的掉用发端一定是store.dispatch(也就是已经compose(f1,f2,f3..store.orgin_dispatch)),这一步不应该产生返回值,否则我要reducer干嘛。
Q2 为何最后还是得有原生的store.dispatch,这其实是废话没有这个就没法进行reducer通知合并action更新state了。
基于上面的论述,我们可以确定两件事情。 第一,middleware中承载的逻辑应该定位为副作用操作,并且不得修改action(但是可以skip)。请记住,不要乱套fp的概念,这个不是monad,本身也不是f1(f2..(..)). 下面的是标准的f1(f2..(..)) ``let f1 = (x) => { x = x + 1 ;return x + 1}
undefined
let f2 = (y) => (2 * y)
undefined
let f3 = (x) => f1(f2(x))
undefined
f3(2)
6 ``
然而,在真实的redux里面却是
| f1 |
| step process 1 |
-------------------- => compose(f1,f2,f3..)
| step process 2 |
| |f2 | | ... |
|------------------|
f1内部先做预处理,然后决定是否call f2,f2内部重复这个过程。
## 2016-5-12更新
第二,middleware中的return next(action) 更准确的应该是 let ret = next(action) -- 官网demo里面 return ret -- 完全不必要 因为 你可以推理 也可以 写console.log加断点跟踪,这些return 最后给谁了? 答案是 store.dispatch(这里的dispatch就是你上面 applyMiddleware增强以后的)。所以,明白了吧,这就是一个常规的嵌套调用,mid1里面 next-》mid2 next->mid3-> ... 这样来的最后的 next(中间件最后)其实 就是 store原生的 dispatch 到这里 我们的action终于给了 reducer。
第三,通过上面的阐述,我可以给一个自己的结论,那就是middleware的设计定位应该是处理和业务无关的副作用操作(比如 websocket,http,db,log等等),且它也同样适合做 filter(不合法的action我不传递了) 和 interceptor(action 增强比如,你穿入user,我附加上user的扩展信息,然后next给后面)
## 2016-5-12 追加更新
刚刚同事问我上面说的对应源代码中的执行细节,包括但不限于何时是参数绑定,何时是函数执行。我从他的问题里面渐渐意识到,原来
网友评论