Redux中间件 结合网络上的资料一些个人理解(我好菜,菜出翔,看大佬的分享依旧看不懂)...

1.中间件怎样在react项目中触发执行与使用

只有发送 action 的这个步骤,即store.dispatch()方法时,才会触发

  • import { applyMiddleware } from "redux";
  • const store = createStore(reducers,applyMiddleware(logger));
  • applyMiddleware(…… , ……) 可以依次放入项目所需用到的中间件

2.createStore()分析

const store = createStore(reducer, preloadedState, enhancer)
  • reducer:即一些改变state的纯函数
  • preloadesState: 是初始化的state;接受整个应用的初始状态作为参数
  • enhancer: store增强功能,在redux中特指applyMiddleware()方法的返回值;一般情况下 createStore 的第三个参数 enhancer 就是 applyMiddlewave
  • 创建store,返回几个函数,主要是dispatch,getState,subscribe,replaceReducer
上源码
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
}

if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
}

if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
}
复制代码

3.一个简易logger中间件的实现示例

// 一个logger中间件 

const logger = store => next => action =>{
    console.log('prevstate',store.getState())
    console.log('dispatch',action);

    let result = next(action);

    console.log('nextstate',store.getState());

    return result;
}

function A(store) {
    return function A(next) {
        return function A(action) {
            /*...*/;
            next(action);
            /*...*/;
            return /*...*/;
        }
    }
}

export default logger;
复制代码
  1. 中间件是一个三阶函数,看他要执行几次才能到最内部的函数代码,就是几阶,统称就是高阶函数。
  2. 三层函数包装:第一层传入store(因此可以使用store的方法),第二层传入下一个中间件next,第三层传入此次执行的action。实现了三层函数嵌套,最后返回 next
  3. 在最内层的函数实现中,需要调用next中间件,并返回结果。

4.applyMiddleware(...middlewares) 源码分析

作用是将所有中间件组成一个数组,依次执行
所有中间件被放进了一个数组chain,然后嵌套执行,最后执行store.dispatch。可以看到,中间件内部(middlewareAPI)可以拿到getState和dispatch这两个方法

//applyMiddleware 源码

(middlewares) => (createStore) => (reducer, preloadedState) => {

    // 第一步先创建一个store
    var store = createStore(reducer, preloadedState, enhancer)
  
    // 缓存dispatch,原store的dispatch要改写。
    var dispatch = store.dispatch
  
    // 定义chain来存放 执行后的二阶中间件
    var chain = []

    // middleware 柯理化的第一个参数。参照logger1的store,这里只保留getState,和改造后的dispatch两个方法。
    var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
    }
  
    // 把中间件处理一层,把getState,dispatch方法传进去,也就是中间件柯理化第一次的store参数。
    // 这样能保证每个中间件的store都是同一个,柯理化的用途就是预置参数嘛。
    chain = middlewares.map(middleware => middleware(middlewareAPI))
  
    // 串联起所有的中间件,dispatch重新赋值,这样调用dispatch的时候,就会穿过所有的中间件。
    dispatch = compose(...chain)(store.dispatch)

    return {
        ...store,
        dispatch
    }
}
复制代码
applyMiddleware() 方法
  • applyMiddleware 一个三阶函数,是用来改写store的dispatch方法,并把所有的中间件都compose串联起来,通过改写dispatch,来实现redux功能的拓展。
  • 接收一个middleware数组作为参数,返回一个方法,就是enhancer;
  • enhancer仍然是一个方法,他接收createStore方法作为参数,enhancer方法返回的还是一个方法,这个方法的参数与createStore方法一样,都包含三个参数,分别是reducer, preloadedState, enhancer;
  • 这个最终的方法的作用有2点,首先调用createStore创建store,然后就是不断的替换store.dispatch,以达到应用中间件的目的;最终就是把原始的store.dispatch应用到所有的中间件的最核心的位置调用,其他的插件都在原始的dispatch外层包裹着。
  • applyMiddlewave 依然使用 createStore 创建了store 对象并且返回,只是改写了这个对象的 dispatch 方法。
默认的redux流程
    view --> dispatch --> action --> reducer --> render --> view
    
applyMiddleware封装之后
    View -> ( mid1 -> mid2 -> …… …… ) -> reducer --> render --> view
    但是经过applyMiddleware的包装,store里面的被封装,在调动action之后,执行封装后的dispatch就会经过一系列的中间件处理,再去触发reducer。
复制代码
中间件的运行原理
  • 将原生的 getState 和 dispacth 作为第一个参数传入中间件数组,获得执行完的 chain 数组:
    chain = middlewares.map(middleware => middleware(middlewareAPI))
  • 组合串联 middleware:
    dispatch = compose(...chain)(store.dispatch)
    compose 将所有的中间件串联起来组成新的 dispatch
何谓柯理化---- 把一个需要传入多个变量的函数变为多个嵌套函数,并且内层函数会调用上层函数的变量。
柯理化函数的核心是闭包,因为闭包,所以内层函数才能够保留父作用域中的变量。
function text1(x, y) {
    return x * y;
}
console.log(add(2, 3)); // 6

function text1(x) {
    return function (y) {
        return x * y;
    }
}

console.log(add2(2)(3)); // 6
复制代码

5.compose源码分析

//compose
其实compose是函数式编程中比较重要的一个方法。上面调用compose的时候可见是一个二阶函数。
 
const compose = (...funcs) => {
  
    //没有参数,那就返回一个function
    if (!funcs.length) {
        return arg => arg
    }
    //一个中间件,返回它
    if (funcs.length === 1) {
        return funcs[0];
    }
    // 最后一个
    var last = funcs[funcs.length -1];
  
    // 复制一份,除去last
    var rest = funcs.slice(0, -1);
  
    // 返回函数,可以接收store.dispatch。
    // reduceRight 反向迭代。
  
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

//reduceRight遍历介绍
[0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) {
    return previousValue + currentValue;
}, 10);

//结果 10+4+3+2+1+0 = 20
复制代码

1 通过chain = middlewares.map(middleware => middleware(middlewareAPI)),得到以下三个中间件的状态变化:

//A
function A(next) {
    return function A(action) {
        /*...*/;
        next(action);
        /*...*/;
        return /*...*/;
    }
}
// B ……
// C ……


2 再由dispatch = compose(...chain)(store.dispatch),我们转化下

const last = C;
const rest = [A,B]
dispatch = rest.reduceRight(
    (composed, f) =>{
        return f(composed)
    }, 
    last(store.dispatch)
)

3 得到的结果
dispatch = A(B(C(store.dispatch)));


4 进一步分析,我们得到的结果:
dispatch = A(B(C(store.dispatch)));

//执行C(next),得到结果

A(B(function C(action) {/*...*/;next(action);/*...*/;return /*...*/;})); 
//此时的next = store.dispatch

//继续执行B(next)
A(function B(action) {/*...*/;next(action);/*...*/;return /*...*/;});    
//此时的next = function C(action) {/*...*/;next(action);/*...*/;return /*...*/;}

//继续执行A(next)
function A(action) {/*...*/;next(action);/*...*/;return /*...*/;};
//此时的next = function B(action) {/*...*/;next(action);/*...*/;return /*...*/;}
复制代码

一个action触发执行顺序,A(action) -> B(action) -> C(action) -> store.dispatch(action)(生产最新的 store 数据); 如果next(action)下面还有需要执行的代码,继续执行 C(next 后的代码)->B(next 后的代码)->A(next 后的代码)

总结:先从内到外生成新的func,然后由外向内执行。本来我们可以直接使用store.dispatch(action),但是我们可以通过中间件对action做一些处理或转换,比如异步操作,异步回调后再执行next;这样的设计很巧妙,只有等待next,才可以继续做操作,和平时直接异步回调又有些不一样

  • 参考我们的 logger middlewave 这里的 composed 即是我们的 next 参数。
  • reduceRight 和 ruduce 一样,不过 reduceRight 是从数组的右端开始执行,arg 将作为 reduceRight 的初始值(这里就是 store.dispatch)。假设我们的 chain 数组为 [f1,f2,f3]执行完毕后 dispatch 为 dispatch = f1(f2(f3(store.dispatch)))),调用这个新的 dispatch 每个中间件就能依次执行了,只有最后一个中间件会触发 redux 原生的 dispatch,将这个 action 分发出去
  • 在applyMiddleware 中 chain数组中都是已经预置middlewareAPI参数后的二阶函数。执行传入的参数都是 形参next。
  • 通过执行compose(...chain)(store.dispatch),last是最后一个中间件,执行并传入 store.dispatch, 返回一个只剩一阶的(action) => {}, 不过已经预置了next参数,也就是store.dispatch
  • 然后last(...args)返回的结果传入reduceRight的回调, 对应形参是composed。
  • f是rest的最后一项, 执行并把 composed 传入,等同于f形参中的next... 得到的结果也是一阶函数,预置的next是last(...args) ...
  • 以此类推。这样,就形成了一个嵌套多层的语句。 类似于logger1(logger2(store.dispatch),当然这只是一个比喻。 只不过到第一个middleware的时候,是二阶函数传入next执行,得到一阶函数返回赋值给dispatch,这时的一阶函数已经变成了形似这样:
function (action) {
  console.log('logger1 start', action);
  next(action);
  console.log('logger1 end', action);
}
复制代码
经过compose之后的dispatch执行
  • 返回的store中dispatch被修改,执行store.dispatch的时候,也就是这个函数执行.
  • 当执行到next(action)的时候,会调用已经预置的next函数,也就是第二个中间件的(action) => {},依次类推。直到最后一个中间件,他的next函数是store.dispatch函数,执行并把action传入。
  • 执行完最后一个中间件的next(action),也就是初始的dispatch。next后面的代码再执行,再逆向把中间件走一遍,直到第一个中间件执行完毕。就会出现这种效果
    start logger1 Object {type: "ADD_TODO", text: "defaultText"}
    start logger2 Object {type: "ADD_TODO", text: "defaultText"}
    dispatch()
    end logger2 Object {type: "ADD_TODO", text: "defaultText"}
    end logger1 Object {type: "ADD_TODO", text: "defaultText"}
    
    dispatch(actions) --> m1 --> next --> m2 --> next --> m3 -->  (store.dispatch(action))  --> m3 --> m2 --> m1
复制代码

6. 中间件的次序也有讲究,有些时候有次序要求

const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);
复制代码

比如,logger(打印日志的中间件)就一定要放在最后,否则输出结果会不正确。

[本文参考地址来自xiexin大佬]juejin.im/post/5a23b1…)

[本文参考地址来自誑逩蝸犇大佬]www.jianshu.com/p/dbe65d95c…)

[本文参考地址来自sunyongjian大佬]github.com/sunyongjian…)

[本文参考地址来自Deot大佬]segmentfault.com/a/119000000…)

[本文参考地址来自yuanyuanispeak大佬]blog.youkuaiyun.com/yuanyuanisp…)

[本文参考地址来自刘一奇大佬]www.liuyiqi.cn/2016/02/02/…)

结语

前端react QQ群:788023830 ---- React/Redux - 地下老英雄

前端交流 QQ群:249620372 ---- FRONT-END-JS前端

(我们的宗旨是,为了加班,为了秃顶……,仰望大佬),希望小伙伴们加群一起学习

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值