资讯专栏INFORMATION COLUMN

React 项目中Redux 中间件的理解

amc / 775人阅读

摘要:如果想学习项目的底层建设,建议先去学习官网案例,之后在学习的使用中间件介绍目的是提供第三方插件的模式,改变的过程。

前言

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, initialState, 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", action);
    console.log("nextState", 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", action);
    console.log("nextState", 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", params, opts));
    net.ajax({
        url: API_ROOT[apiName],
        type: ajaxType,
        param,
        localData,
        success: data => {
            onSuccess && onSuccess(data);
            params={
                ...params,
                data
            };
            //触发请求成功的action
            return next(nextAction(apiName + "_SUCCESS", params, opts));
        },
        error: data => {
            onError && onError(data);
            //触发请求失败的action
            return next(nextAction(apiName + "_ERROR", params, 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, params, {});

其对应的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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86545.html

相关文章

  • React 328道最全面试题(持续更新)

    摘要:希望大家在这浮夸的前端圈里,保持冷静,坚持每天花分钟来学习与思考。 今天的React题没有太多的故事…… 半个月前出了248个Vue的知识点,受到很多朋友的关注,都强烈要求再出多些React相前的面试题,受到大家的邀请,我又找了20多个React的使用者,他们给出了328道React的面试题,由我整理好发给大家,同时发布在了前端面试每日3+1的React专题,希望对大家有所帮助,同时大...

    kumfo 评论0 收藏0
  • React专题:reactredux以及react-redux常见一些面试题

    摘要:我们可以为元素添加属性然后在回调函数中接受该元素在树中的句柄,该值会作为回调函数的第一个参数返回。使用最常见的用法就是传入一个对象。单向数据流,比较有序,有便于管理,它随着视图库的开发而被概念化。 面试中问框架,经常会问到一些原理性的东西,明明一直在用,也知道怎么用, 但面试时却答不上来,也是挺尴尬的,就干脆把react相关的问题查了下资料,再按自己的理解整理了下这些答案。 reac...

    darcrand 评论0 收藏0
  • 高级前端面试题大汇总(只有试题,没有答案)

    摘要:面试题来源于网络,看一下高级前端的面试题,可以知道自己和高级前端的差距。 面试题来源于网络,看一下高级前端的面试题,可以知道自己和高级前端的差距。有些面试题会重复。 使用过的koa2中间件 koa-body原理 介绍自己写过的中间件 有没有涉及到Cluster 介绍pm2 master挂了的话pm2怎么处理 如何和MySQL进行通信 React声明周期及自己的理解 如何...

    kviccn 评论0 收藏0
  • React Redux 间件思想遇见 Web Worker 灵感(附demo)

    摘要:写在最前原文首发于作者的知乎专栏中间件思想遇见的灵感附,感兴趣的同学可以知乎关注,进行交流。其中,最重要的一个便是对多线程的支持。在中提出了工作线程的概念,并且规范出的三大主要特征能够长时间运行响应理想的启动性能以及理想的内存消耗。 写在最前 原文首发于作者的知乎专栏:React Redux 中间件思想遇见 Web Worker 的灵感(附demo),感兴趣的同学可以知乎关注,进行交流...

    whatsns 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<