加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

React 项目中Redux 中间件的理解

发布时间:2020-12-15 08:16:15 所属栏目:百科 来源:网络整理
导读:前言 React/Redux项目结束后,当我在研究react-router源码的时候发现当中有一部分含中间件的思想,所以才想把中间件重新梳理一遍;在之前看redux了解到中间件,redux层面中间件的理解对项目前期比较有帮助,虽然项目中后期基本可以忽略这层概念;现在对这部

前言

React/Redux项目结束后,当我在研究react-router源码的时候发现当中有一部分含中间件的思想,所以才想把中间件重新梳理一遍;在之前看redux了解到中间件,redux层面中间件的理解对项目前期比较有帮助,虽然项目中后期基本可以忽略这层概念;现在对这部分的笔记重新梳理,这里只针对这个中间件做一个理解。

如果想学习项目的底层建设,建议先去学习官网redux案例,之后在学习react-router的使用

Redux 中间件介绍

Redux 目的是提供第三方插件的模式,改变action -> reducer 的过程。变为 action -> middlewares -> reducer 。自己在项目中使用它改变数据流,实现异步 action ;下面会对日志输出做一个开场。

使用 Redux 中间件

Redux 中 applyMiddleware 的方法,可以应用多个中间件,这里先只写一个中间件,以日志输出中间件为例

//利用中间件做打印log
import {createStore,applyMiddleware} from 'redux';
import logger from '../api/logger';
import rootReducer from '../reducer/rootReducer';


let createStoreWithMiddleware = applyMiddleware(logger)(createStore);
let store = createStoreWithMiddleware(rootReducer);
// 也可以直接这样,可以参考createStore
// createStore(
//     rootReducer,//     applyMiddleware(logger)
// )
export default store;

logger 中间件结构分析

const logger = store => next => action => {
    let result = next(action); // 返回的也是同样的action值
    console.log('dispatch',action);
    console.log('nextState',store.getState());
    return result;
};

export default logger;

store => next => action =>{} 实现了三层函数嵌套,最后返回 next ,给下一个中间件使用,接下来把三层函数拆解;

从applyMiddleware源码开始分析

///redux/src/applyMiddleware.js
export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer,initialState,enhancer) => {
        var store = createStore(reducer,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
        }
    }
}
最外层store
//源码分析
chain = middlewares.map(middleware => middleware(middlewareAPI));

我们发现store是middlewareAPI,

//store
var middlewareAPI = {
    getState: store.getState,dispatch: (action) => dispatch(action)
}

然后就剩下

next => action => {
    let result = next(action); // 返回的也是同样的action值
    console.log('dispatch',store.getState());
    return result;
};
中间层next
//源码分析
dispatch = compose(...chain)(store.dispatch)

先来分析compose(...chain)

//compose源码
export default function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg
    }

    if (funcs.length === 1) {
        return funcs[0]
    }

    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0,-1)
    return (...args) => rest.reduceRight((composed,f) => f(composed),last(...args))
}

compose利用Array.prototype.reduceRight的方法

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

//结果 10+4+3+2+1+0 = 20

因为我们这里的中间件就只有一个,所以没有使用到reduceRight直接返回,直接返回func[0](本身);再由compose(...chain)(store.dispatch),我们可以知道next就是store.dispatch

(action) => {
    let result = store.dispatch(action); // 这里的next就是store.dispatch
    console.log('dispatch',store.getState());
    return result;
};

我们之后调用的dispath就是触发的是上面这个函数(这里就单个中间件);

多个中间件

  • 通过上面的 applyMiddleware,compose 和中间件的结构,

  • 假设应用了如下的中间件: [A,B,C],这里我们使用es5的结构做分析

  • 分析action触发的完整流程

三个中间件

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

通过chain = middlewares.map(middleware => middleware(middlewareAPI)),三个中间件的状态变化

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

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

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

我们得到的结果

dispatch = A(B(C(store.dispatch)));

进一步分析,我们得到的结果

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,才可以继续做操作,和平时直接异步回调又有些不一样

项目实践 ->异步

我们知道redux中actions分为actionType,actionCreator,然后在由reducer进行修改数据;

官方例子中async直接在actionCreator做了ajax请求;

我们把ajax放入中间件触发下面要讲的与官方real-world类似

我这边使用redux-thunk

applyMiddleware(reduxThunk,api)

先来看看redux-thunk的源码

function createThunkMiddleware(extraArgument) {
    return ({ dispatch,getState }) => next => action => {
        if (typeof action === 'function') {//重新分发
            return action(dispatch,getState,extraArgument);
        }
        return next(action);//传递给下一个中间件
    };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

这样一来我们可以把异步写成一个复用的actionCreator;

import * as types from '../../constants/actions/common';

export function request(apiName,params,opts = {}) {
    return (dispatch,getState) => {
        let action = {
            'API': {
                apiName: apiName,params: params,opts: opts
            },type: types.API_REQUEST
        };
        return dispatch(action);
    };
}


//其他地方调用复用的方法如下:
export { request } from './request';

正常的写法,不是异步的,就是之前的写法

export function cartSelect(id) {
    return { 
        type: types.CART_MAIN_SELECT,id
    };
}

然后就是下一个中间件的处理 api.js

//自己封装的ajax,可以使用别的,比如isomorphic-fetch
import net from 'net';
//项目中全部的接口,相当于一个关于异步的actionType有一个对应的后端接口
import API_ROOT from 'apiRoot';

export default store => next => action => {
    let API_OPT = action['API'];

    if (!API_OPT) {
        //我们约定这个没声明,就不是我们设计的异步action,执行下一个中间件
        return next(action);
    }

    let ACTION_TYPE = action['type'];
    let { apiName,params = {},opts = {} } = API_OPT;
    /**
     * 如果有传递localData,就不会触发ajax了,直接触发_success
     * 当前也可以传其他参数
     */
    let { localData } = opts;
    let {
        onSuccess,onError,onProgress,ajaxType = 'GET',param
    } = params;
    // 触发下一个action
    let nextAction = function(type,param,opts) {
        action['type'] = type;
        action['opts'] = opts;
        delete param['onSuccess'];
        delete param['onError'];
        const nextRequestAction = {...action,...param}
        return nextRequestAction;
    };

    params={
        ...params,data: null
    };
    // 触发正在请求的action
    let result = next(nextAction(apiName + '_ON',opts));
    net.ajax({
        url: API_ROOT[apiName],type: ajaxType,localData,success: data => {
            onSuccess && onSuccess(data);
            params={
                ...params,data
            };
            //触发请求成功的action
            return next(nextAction(apiName + '_SUCCESS',opts));
        },error: data => {
            onError && onError(data);
            //触发请求失败的action
            return next(nextAction(apiName + '_ERROR',opts));
        }
    });

    return result;
};

强调一点:项目中全部的接口,相当于一个关于异步的actionType有一个对应的后端接口,所以我们才可以通过API_ROOT[apiName]找到这个接口

以cart为列子(下面是对应的每个文件):

actionType:

//异步
export const CART_MAIN_GET = 'CART_MAIN_GET';
//非异步
export const CART_MAIN_SELECT = 'CART_MAIN_SELECT';

api:

const api = {
    'CART_MAIN_GET':'/shopping-cart/show-shopping-cart'
};
export default api;

APIROOT修改:

import cart from './api/cart';
const APIROOT = {
    ...cart
};
export default API;

actionCreator:

//项目中使用redux的bindActionCreators做一个统一的绑定,所以在这里单独引入
export { request } from './request';
//下面是非异步的方法
export function cartSelect(id) {
    return { 
        type: types.CART_MAIN_SELECT,id
    };
}

项目中发起结构是这样的:

let url = types.CART_MAIN_GET;
let param = {};
let params = {
    param: param,ajaxType: 'GET',onSuccess: (res) => {
        /*...*/
    },onError: (res) => {
        /*...*/
    }
};
request(url,{});

其对应的reducers就是下面

import * as types from '../constants/actions/cart';
const initialState = {
    main:{
        isFetching: 0,//是否已经获取 
        didInvalidate:1,//是否失效
        itemArr:[],//自定义模版
        itemObj:{},//自定义模版数据
        header:{}//头部导航
    }
};
export default function(state = initialState,action) {
    let newState;
    switch (action.type) {
        case types.HOME_MAIN_GET + '_ON'://可以不写
            /*...*/
            return newState;
        case types.HOME_MAIN_GET + '_SUCCESS':
            /*...*/
            return newState;
        case types.HOME_MAIN_GET + '_ERROR'://可以不写
            /*...*/
            return newState;
        default:
            return state;
    }
};

异步,数据验证都可以通过中间件做处理;引用Generator,Async/Await,Promise处理,可以参考社区中的一些其他方式,比如:

  • redux-promise

  • redux-saga

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读