Open fi3ework opened 6 years ago
就是拦截函数类型的action,再能够对函数形式的 action(其实是个 actionCreator)暴露 API 再执行一次,如果这个 actionCreator 是多层函数的嵌套,则必须每次执行 actionCreator 后的 actionCreator 都可以引用最新的 dispatch 才行。如果不写成匿名函数,那这个 actionCreator 又走了没有经过任何中间件修饰的 store.dispatch,这显然是不行的。所以要写成匿名函数的闭包引用
这段能在描述清楚一点吗? 为什么要写匿名函数,怎么就是引用了闭包了?
感谢,受益良多
感谢 唯一一篇写的比较清楚的
感谢!! 有个小笔误 ' 返回到 M2 打印 E ' -> ' 返回到 M2 打印 D '
前言
最近翻出了之前分析的 applyMiddleware 发现自己又看不懂了😳,重新看了一遍源代码,梳理了洋葱模型的实现方法,在这里分享一下。
applyMiddleware源码解析
applyMiddleware 函数虽短但却是 Redux 最精髓的地方,成功的让 Redux 在保持“自身函数式纯洁”的前提下,在 action 传递的过程中插入了提供了副作用的空间。 这个 middleware 的洋葱模型思想是从 koa 的中间件拿过来的,用图来表示最直观。
上图之前先上一段用来示例的代码(via 中间件的洋葱模型),我们会围绕这段代码理解 applyMiddleware 的洋葱模型机制:
再放上 Redux 的洋葱模型的示意图(via 中间件的洋葱模型),以上代码中间件的洋葱模型如下图:
我们将每个 middleware 真正带来副作用的部分(在这里副作用是好的,我们需要的就是中间件的副作用),称为
M?副作用
,它的函数签名是(action) => {}
(记住这个名字)。对这个示例代码来说,Redux 中间件的洋葱模型运行过程就是:
用户派发 action → action 传入 M1 副作用 → 打印 A → 执行 M1 的 next(这个 next 指向 M2 副作用)→ 打印 C → 执行 M2 的 next(这个 next 指向 M3 副作用)→ 打印 E → 执行 M3 的 next(这个 next 指向
store.dispatch
)→ 执行完毕返回到 M3 副作用打印 F → 返回到 M2 打印 E → 返回到 M1 副作用打印 B -> dispatch 执行完毕。那么问题来了,M1 M2 M3的 next 是如何绑定的呢?
答:柯里化绑定,一个中间件完整的函数签名是
store => next => action {}
,但是最后执行的洋葱模型只剩下了 action,外层的 store 和 next 经过了柯里化绑定了对应的函数,接下来看一下 next 是如何绑定的。关键点就是两句绑定,先来看第一句
为什么要绑定
getState
?因为中间件需要随时拿到当前的 state,为什么要拿到dispatch
?因为中间件中可能会存在派发 action 的行为(比如 redux-thunk),所以用这个 map 函数柯里化绑定了getState
和dispatch
。此时
chain = [(next)=>(action)=>{…}, (next)=>(action)=>{…}, (next)=>(action)=>{…}]
,…
里闭包引用着dispatch
和getState
。接下来
dispatch = compose(...chain)(store.dispatch)
,先了解一下compose
函数这就是 compose 的作用,从右至左依次将右边的返回值作为左边的参数传入,层层包裹起来,在 React 中嵌套 Decorator 就是这么写,比如:
再说回 Redux
在实例代码中相当于
MC 就是 chain 中的元素,没错,这又是一次柯里化。
至此,真相大白,dispatch 做了一点微小的贡献,一共干了两件事:1. 绑定了各个中间件的 next。2. 暴露出一个接口用来接收 action。其实说了这么多,middleware 就是在自定义一个dispatch,这个 dispatch 会按照洋葱模型来进行 pipe。
OK,到现在我们已经拿到了想要的 dispatch,返回就可以收工了,来看最终执行的灵魂一图流:
细节
然而可达鸭眉头一皱,发现事情还没这么简单,有几个问题要想一下
dispatch
在这里 dispatch 使用匿名函数是为了能在 middleware 中调用 compose 的最新的 dispatch(闭包),必须是匿名函数而不是直接写成 store.dispatch。
如果直接写成
store.dispatch
,那么在某个 middleware(除最后一个,最后一个middleware拿到的是原始的store.dispatch
)dispatch 一个 action,比如 redux-thunk就是拦截函数类型的 action,再能够对函数形式的 action(其实是个 actionCreator)暴露 API 再执行一次,如果这个 actionCreator 是多层函数的嵌套,则必须每次执行 actionCreator 后的 actionCreator 都可以引用最新的 dispatch 才行。如果不写成匿名函数,那这个 actionCreator 又走了没有经过任何中间件修饰的
store.dispatch
,这显然是不行的。所以要写成匿名函数的闭包引用。还有,这里使用了
...args
而不是action
,是因为有个 PR,这个 PR 的作者认为在 dispatch 时需要提供多个参数,像这样dispatch(action, option)
,这种情况确实存在,但是只有当这个需提供多参数的中间件是第一个被调用的中间件时(即在 middlewares 数组中排最后)才肯定有效 ,因为无法保证上一个调用这个多参数中间件的中间件是使用的 next(action) 或是 next(...args) 来调用,所以被改成了 next(…args) ,在这个 PR 的讨论中可以看到 Dan 对这个改动持保留意见(但他还是改了),这个改动其实真的挺蛋疼的,我作为一个纯良的第三方中间件,怎么能知道你上个中间件传了什么乱七八糟的属性呢,再说传了我也不知道是什么意思啊大哥。感觉这就是为了某些 middleware 能够配合使用,不想往 action 里加东西,就加在参数中了,到底是什么参数只有这些有约定好参数的 middleware 才能知道了。redux-logger
要求必须把自己放在 middleware 的最后一个,理由是
试想,logger 想 log 什么?就是
store.dispatch
时的信息,所以 logger 肯定要在store.dispatch
的前后 console,还记不记得上面哪个中间件拿到了 store.dispatch,就是最后一个,如果把logger
放在第一个的话你就能打出所有的action
了,比如redux-thunk
的 actionCreator,打印的数量肯定比放在最后一个多,因为并不是所有的 action 都能走到最后,也有新的 action 在 middleware 在中间被派发。参考
redux middleware 详解
Redux 进阶教程
redux applyMiddleware 原理剖析
绘图