现在我们希望每次状态更改时,打印出状态更改前后的变化
我们知道状态的更改是redux内部触发了diaptch才更改的
如果我们要完成上面的需求,书写逻辑就应该是
console.log('老状态', store.getState());
dispatch(action)
console.log('新状态', store.getState());
当然我们不可能为了这个需求就跑到redux内部源码去添加这两句
我们可以在创建store使用代理的思想来做个劫持
let store = createStore(reducers);
let dispatch = store.dispatch
store.dispatch = function (action) {
console.log('老状态', store.getState());
dispatch(action)
console.log('新状态', store.getState());
}
这样就可以完美的实现了我们的需求
compose
如果又有新的需求
let store = createStore(reducers);
/**
* 中间件1
*/
let dispatch = store.dispatch
store.dispatch = function (action) {
console.log('老状态', store.getState());
dispatch(action)
console.log('新状态', store.getState());
}
/**
* 中间件2
*/
let dispatch_2 = store.dispatch
store.dispatch = function (action) {
console.log('老状态__2', store.getState());
dispatch_2(action)
console.log('新状态__2', store.getState());
}
这个打印顺序立马就让我们想到了洋葱模型
从洋葱图和上面代码的引导
我们知道所谓的中间件就是一个增强的dispatch
对于A来说B就是一个dispatch
这就像流水线上产品
为了把上面的中间件代码优化
我们先来思考下怎么用函数的方式完成这种流水线加工的过程
function add1(dispatch) {
return '|→' + dispatch + '←|'
}
function add2(dispatch) {
return '||-→' + dispatch + '←-||'
}
function add3(dispatch) {
return '|||--→' + dispatch + '←--|||'
}
let dispatch = add3(add2(add1('dispatch')))
console.log(dispatch);
|||--→||-→|→dispatch←|←-||←--|||
我们现在的需求是有一个compose方法
// let dispatch = add3(add2(add1('dispatch')))
// console.log(dispatch);
let add = compose(add3, add2, add1)
let dispatch = add('dispatch')
调用compose方法后他会把全部加工工序化为一个总的加工工序add
那么我们调用时只需要调用add()并把原材料传递进入
就能达到上面的效果
function compose(...fns) {
// 返回全部加工工序的合计,即上面的 add
return function (...args) { // 接受原材料 即 dispatch
// 取出最后一个加工工序 即 add1
let last = fns.pop()
fns.reduceRight(() => {
}, last(...args))
}
}
这里使用了
reduceRightlet arr = ['c', 'b', 'a'] let last = arr.pop() console.log(last); // 'a' console.log(arr); // [ 'c', 'b' ] let ret = arr.reduceRight((prev, cur) => prev + cur, last) console.log(ret); // abc
reduceRight表示从右往左执行回调函数
(prev, cur) => prev + cur里的参数prev是该函数上次执行的返回结果如果
reduceRight有第二个参数,如reduceRight((prev, cur) => prev + cur, last)这里的last那么就会作为第一次执行回调函数的
prev所以执行第一次回调时
(prev, cur) => prev + cur上面的
prev就是last即,a然后
cur就是arr从右往左开始的数,即b所以代码执行的就是
'a' + 'b'然后返回a+b的结果,即'ab'第二次执行回调时
(prev, cur) => prev + cur此时的
prev就是第一次回调的返回的值,即ab
cur就是c,所以最后的结果就是'ab'+'c'即abc
现在再回到代码里来
function compose(...fns) {
console.log(fns); //[ [Function: add3], [Function: add2], [Function: add1] ]
// 返回全部加工工序的合计,即上面的 add
return function (...args) { // 接受原材料 即 dispatch
console.log(args); //[ 'dispatch' ]
// 取出最后一个加工工序 即 add1
let last = fns.pop()
console.log(last); // [Function: add1]
// 加工工序的合计 即 add 最后把加工的物品返回
return fns.reduceRight((val, fn) => fn(val), last(...args))
}
}
上面的核心就只是fns.reduceRight((val, fn) => fn(val), last(...args))
-
第一次执行回调
(val, fn) => fn(val)这里的
val就是last(...args),即我们代码中的add1('dispatch')这里的
fn就是fns从右往左的第一个fn(val)展开的代码就是add2(add1('dispatch')) -
第二次执行回调
(val, fn) => fn(val)此是的
val就是上次回调的返回结果,即add2(add1('dispatch'))那么
fn就是add3所以
fn(val)就是add3(add2(add1('dispatch')))
此时fns.reduceRight就全部执行完毕,最后返回的就是add3(add2(add1('dispatch')))
所以上面的代码就是
function compose(...fns) {
return function (...args) {
return add3(add2(add1(...args)))
}
}
let add = compose(add3, add2, add1)
此时的add就是
let add = function (...args) {
return add3(add2(add1(...args)))
}
let dispatch = add('dispatch')
我们现在来回顾下我们做了什么
-
我们有一个
dispatch -
我们希望这个
dispatch在流水线上加工let dispatch = add3(add2(add1('dispatch'))) -
我们希望把全部工序合并,调用合并后的接口即可
let add = compose(add3, add2, add1) let dispatch = add('dispatch')-
我们创建一个
compose函数-
接受所有加工工序
compose(add3, add2, add1) -
返回合并后的工序,调用合并后的工序即可返回最终结果
let add = compose(add3, add2, add1) let dispatch = add('dispatch')
-
-
而compose的核心就是使用reduceRight
- 接受加工工序的集合
- 返回一个总的加工工序
function compose(...fns){
return function addAll(){
}
}
那么这个addAll的参数就是原材料了
function addAll(...args){
// ret1 = fn1(...args)
// ret2 = fn2(ret1)
// ret3 = fn3(ret2)
// ...
// ret = fnN(retN)
return ret
}
因为每次fn调用的都是上一个函数执行后返回的结果
我们立马就会想到reduce
let arr = [1, 2, 3]
let ret = arr.reduce((prev, cur) => prev + cur)
console.log(ret); // 6
最终我们就完成了
function compose(...fns){
return function(...args){
let last = fns.pop()
return fns.reduceRight((val,fn) => fn(val),last(...args))
}
}
好了 总结完了
但是这个compose写的还有点复杂
还有一个更优雅的写法
let add = compose(add3, add2, add1)
function compose(...fns) {
return fns.reduce((a, b) => (...args) => a(b(...args)))
}
现在有了上面reduceRight的基础,我们再来看reduce就容易多了
reduce就是从左到右的,而且这里没有使用reduce的第二个参数
-
第一次执行回调
因为这里没有第二个参数,所以
a就是fns里的第一个元素,即add3那么
b就是add2所以第一次回调返回的是
function (...args){ return add3(add2(...args)) } -
第二次执行回调
此时的
a就是上面的返回结果,即let a = function (...args){ return add3(add2(...args)) }那
b就是add1a(b(...args))这里就是最巧妙的地方了
我们知道
a现在是let a = function (...args){ return add3(add2(...args)) }我们现在把
a的参数,即(...args)替换是b,即add1(...args)function (...args){ return add3(add2(add1(..args))) }这样就达到上面的效果了
这里巧妙的就是回调函数返回的是一个函数
(...args) => prev(cur(...args))然后把
cur作为该函数的参数就可以达到嵌套的效果即,add3(add2(add(...args)))当然这里有个前提是
fns的长度要等于2所以我们要添加一个句
function compose(...fns) { if (fns.length === 1) return fns[0] return fns.reduce((a, b) => (...args) => a(b(...args))) }
中间件写法
我们说了这么久的compose差点快忘了我们的初衷
let store = createStore(reducers);
/**
* 中间件1
*/
let dispatch = store.dispatch
store.dispatch = function (action) {
console.log('老状态', store.getState());
dispatch(action)
console.log('新状态', store.getState());
}
/**
* 中间件2
*/
let dispatch_2 = store.dispatch
store.dispatch = function (action) {
console.log('老状态__2', store.getState());
dispatch_2(action)
console.log('新状态__2', store.getState());
}
因为我们知道其实所谓的中间件就是对dispatch进行加工
我们现在希望的是
let dispatch = store.dispatch
let middleware1 = {}
let middleware2 = {}
let middleware3 = {}
let middleware = compose(middleware1, middleware2, middleware)
store.dispatch = middleware
这个合并的目标我们已经达成了
现在我们的问题来了
我们应该怎么把
/**
* 中间件1
*/
let dispatch = store.dispatch
store.dispatch = function (action) {
console.log('老状态', store.getState());
dispatch(action)
console.log('新状态', store.getState());
}
转换成一个函数形式的
let middleware1 = {}
首先中间件是一个dispatch,那么框架就是
let middleware = function(action){
dispatch(action)
}
根据我们加工厂的例子
function add1(dispatch) {
return '|→' + dispatch + '←|'
}
function add2(dispatch) {
return '||-→' + dispatch + '←-||'
}
function add3(dispatch) {
return '|||--→' + dispatch + '←--|||'
}
let dispatch = add3(add2(add1('dispatch')))
console.log(dispatch);
我们的中间件应该要接受一个dispatch以确定获得是加工过的dispatch
那我们就把代码改成
let middleware = function(dispatch){
return function(action){
// 逻辑代码
dispatch(action)
// 逻辑代码
}
}
那到时我们就把middleware()执行再返回,就达到了以下两个需求
middleware返回一个加工后的dispatch需求middleware对流水线上的dispatch进行加工
现在一切都很完美
但是我们突然想到我们的逻辑代码
/**
* 中间件1
*/
let dispatch = store.dispatch
store.dispatch = function (action) {
console.log('老状态', store.getState());
dispatch(action)
console.log('新状态', store.getState());
}
会使用getState
这就尴尬了,我们这个getState没有办法获取
于是我们只能使用老套路,用函数套函数的方式
let middleware = function(store){
return function(dispatch){
return function(action){
// 逻辑代码
// store.getState()
dispatch(action)
// 逻辑代码
}
}
}
那就有人好奇为什么不直接
let middleware = function(store){
return function(action){
// 逻辑代码
store.dispatch(action)
// 逻辑代码
}
}
因为这样我们的层次就没有了,参数的位置也需要规定
而且使用函数套函数的写法能使得该函数具有更高的可复用性,即解耦合
因为我们有时候的需求可能只是
fn()返回的结果
有时候的需求又可能是fn()()返回后的结果
let store = createStore(reducers);
let dispatch = store.dispatch
let middleware1 = {}
let middleware2 = {}
let middleware3 = {}
let middlewares = [middleware1, middleware2, middleware3]
// 获取到了 store 这样就可以使用 store.getState
middlewares = middlewares.map(middleware => middleware(store))
// 流水线合并 获取总的加工工序集合
add = compose(...middlewares)
// 往加工工序里添加原材料 即 最原始的 store.dispatch
store.dispatch = add(dispatch)
那我们就知道中间件的最终写法就是
let middleware = function ({ getState }) {
return function (dispatch) {
return function (action) {
// 逻辑代码 getState
dispatch()
// 逻辑代码 getState
}
}
}
applyMiddleware
我们通过使用compose和确定middleware的方法使得中间件可以复用
但是代码还是特别的冗长
let store = createStore(reducers);
let dispatch = store.dispatch
let middleware1 = {}
let middleware2 = {}
let middleware3 = {}
let middlewares = [middleware1, middleware2, middleware3]
middlewares = middlewares.map(middleware => middleware(store))
add = compose(...middlewares)
store.dispatch = add(dispatch)
我们希望把以下三个过程合并成一个函数
// 创建store
let store = createStore(reducers);
// 截取 store.dipatch
let dispatch = store.dispatch
// 给store.dispatch 绑定成我们加工后的 dispatch
let middlewares = [middleware1, middleware2, middleware3]
middlewares = middlewares.map(middleware => middleware(store))
let add = compose(...middlewares)
store.dispatch = add(dispatch)
我们最终期望的是执行一个函数就返回好处理过dispatch的store
const store = applyMiddleware(...)
那我们就知道
let applyMiddleware = function () {
let store = createStore(reducer)
let dispatch = store.dispatch
let middlewares = [middleware1, middleware2, middleware3]
middlewares = middlewares.map(middleware => middleware(store))
store.dispatch = compose(...middlewares)(dispatch)
return store
}
这样写确实是使用函数完成了,但是我们思考一下我们应该怎么写参数呢
applyMiddleware就一定会接受三个参数
middlewaresreducercreateStore
这个时候我们就会又想到中间件的函数套函数的写法
来确定参数的层级关系和解耦合
那我们就把上面三个参数分有三层来处理
如果是你你会思考着按什么顺序调用呢?
既然叫applyMiddleware那我们当然希望最贴近的参数是Middleware
const store = applyMiddleware(middleware1,middleware2,middleware3)
那我们还要传递createStore和reducer
既然平时使用这两个参数是使用
let store = createStore(reducer)
我们就按这种顺序传递
const store = applyMiddleware(middleware1,middleware2,middleware3)(createStore)(reducer)
那applyMiddleware的写法应该就是
let applyMiddleware = function (...middlewares) {
return function (createStore) {
return function (reducer) {
let store = createStore(reducer)
let dispatch = store.dispatch
middlewares = middlewares.map(middleware => middleware(store))
store.dispatch = compose(...middlewares)(dispatch)
return store
}
}
}
代码已经可以跑通了
我们思考下middleware(store)
我们突然觉得不应该直接把整个store传递给他
因为store上有很多方法的
如果我们在写中间件哪里不小心调用了
这个错误就很难找到了
let middlewareAPI = {
getState: store.getState,
}
middlewares = middlewares.map(middleware => middleware(middlewareAPI))
我们还发现
let dispatch = store.dispatch
store.dispatch = compose(...middlewares)(dispatch)
这里写的很累赘,合并起来就可以了
let applyMiddleware = function (...middlewares) {
return function (createStore) {
return function (reducer) {
let store = createStore(reducer)
let middlewareAPI = {
getState: store.getState,
}
middlewares = middlewares.map(middleware => middleware(middlewareAPI))
store.dispatch = compose(...middlewares)(store.dispatch)
return store
}
}
}
createStore
其实,说实在的
const store = applyMiddleware(logger, logger1)(createStore)(reducer)
这种写法真的挺丑的
我们看官方文档提供了一种比较好的写法
const store = createStore(reducer, ['Use Redux'], applyMiddleware(logger, logger1))
这就使用到了createStore的第二个和第三个参数
export default function createStore(reducer, preloadedState, enhancer) {
if (enhancer) {
return enhancer(createStore)(reducer, preloadedState);
}
//...
}
仔细一看就知道只是把后面的执行部分放在了源码里面实现
从这里我们就更可以明白了为什么使用函数套函数的写法了
因为这里我们只是需要applyMiddleware()的结果作为值返回
如果我们使用了applyMiddleware(middlewares,createStore,reducer)的写法
那``applyMiddleware`就写死了