热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入理解React项目中的Redux中间件及其应用

在深入研究React项目的过程中,特别是在探索react-router源码时,我发现了其中蕴含的中间件概念。这激发了我对中间件的进一步思考与整理。本文将详细探讨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, 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


推荐阅读
  • 在使用 `useSelector` 选择器时,发现分派操作后状态未能实时更新。这可能是由于 React 组件的渲染机制或 Redux 的状态管理问题导致的。建议检查 `useSelector` 的依赖项和 `dispatch` 的调用时机,确保状态变化能够正确触发组件重新渲染。此外,可以考虑使用 `useEffect` 钩子来监听状态变化,以确保及时更新。 ... [详细]
  • 大家好,我是梅巴哥er。本文将深入探讨Redux框架中的第三个实战案例,具体实现每两秒自动点击按钮以触发颜色变化的功能。该案例中,一个关键点在于是否需要使用异步操作来处理定时任务,我们将详细分析其必要性和实现方式。通过这一实例,读者可以更好地理解Redux在实际项目中的应用及其异步处理机制。 ... [详细]
  • React项目基础教程第五课:深入解析组件间通信机制 ... [详细]
  • 深入解析 Vue3 中的响应式 API:shallowReactive、shallowRef、triggerRef 和 customRef 的使用与原理
    深入解析 Vue3 中的响应式 API:shallowReactive、shallowRef、triggerRef 和 customRef 的使用与原理 ... [详细]
  • 深入解析 Vue 中的 Axios 请求库
    本文深入探讨了 Vue 中的 Axios 请求库,详细解析了其核心功能与使用方法。Axios 是一个基于 Promise 的 HTTP 客户端,支持浏览器和 Node.js 环境。文章首先介绍了 Axios 的基本概念,随后通过具体示例展示了如何在 Vue 项目中集成和使用 Axios 进行数据请求。无论你是初学者还是有经验的开发者,本文都能为你解决 Vue.js 相关问题提供有价值的参考。 ... [详细]
  • Egg.js 中间件详解与应用实例
    Egg.js 的中间件机制与 Koa 类似,均采用洋葱模型。每当开发一个中间件时,就像是在洋葱外增加了一层。本文将通过一个简单的中间件示例,详细介绍 Egg.js 中间件的编写方法及其应用场景,帮助读者更好地理解和使用这一功能。 ... [详细]
  • 本文深入探讨了 Vue 框架中的混入(mixins)机制及其实际应用场景。首先,文章从官方文档出发,详细解读了混入的基本概念和核心原理。接着,通过具体的代码示例,展示了如何创建和使用混入,帮助开发者更好地理解和掌握这一功能。此外,文章还对比了混入与 Vuex、公共组件之间的区别,明确了各自适用的场景和优缺点,为开发者在项目中选择合适的技术方案提供了参考。 ... [详细]
  • InnoDB当前仅支持一次创建一个FULLTEXT索引 ... [详细]
  • 在PHP的设计中,预定义了9个超级全局变量、8个魔术变量和13个魔术函数,这些变量和函数无需声明即可在脚本的任意位置使用。这些特性在PHP开发中极为常见,能够显著提升开发效率和代码的灵活性。相比之下,Java并没有类似的内置机制,但通过其他方式如上下文对象和反射机制,也可以实现类似的功能。本文将详细探讨这两种语言中这些特殊变量和函数的使用方法及其应用场景。 ... [详细]
  • 在Kubernetes上部署多个Mitmproxy代理服务器以实现高效流量管理 ... [详细]
  • 优化升级版数据采集与赋值方法,专为前文内容设计
    在前一篇文章中,方法的局限性主要体现在需要传递参数,并且参数数量受限。当页面布局与所需参数不匹配时,该方法将无法正常工作。为此,我们推出了优化升级版1.1,旨在解决这些问题并提高灵活性和适用性。 ... [详细]
  • 深入解析JavaScript中的函数防抖与节流技术及其应用场景
    本文深入探讨了JavaScript中函数防抖和节流技术的原理及应用场景。通过详细的示例代码,全面解析了这两种优化方法在实际开发中的重要作用,为开发者提供了宝贵的参考和实践指导。 ... [详细]
  • 2017年12月7日:React中实现不同组件间的路由导航
    在React应用中,实现从首页到不同组件的路由导航是常见的需求。本文介绍了如何通过配置路由来实现这一功能。具体步骤包括:1. 在首页设置路由链接,使其能够跳转到不同的目标组件;2. 确保目标组件正确配置,特别是在导入时使用默认导出(`default`),以确保路由能够正常工作。此外,文章还提供了详细的代码示例,帮助开发者更好地理解和实现这一功能。 ... [详细]
  • GDB 使用心得与技巧总结
    在使用 GDB 进行调试时,可以采用以下技巧提升效率:1. 通过设置 `set print pretty on` 来美化打印输出,使数据结构更加易读;2. 掌握常见数据结构的打印方法,如链表、树等;3. 利用 `info locals` 命令查看当前作用域内的所有局部变量;4. 在需要进行类型强制转换时,正确使用语法,例如 `p (Test::A *) pObj`。这些技巧能够显著提高调试的便捷性和准确性。 ... [详细]
  • ZTree工具类全面汇总:实现节点的增删改及后台提交功能
    本文全面总结了ZTree工具类的使用方法,详细介绍了如何实现节点的增加、删除、修改以及后台数据提交等功能。通过实例代码和具体操作步骤,帮助开发者高效地掌握ZTree的各类操作,提升开发效率。此外,还提供了常见问题的解决方案,如在SpringBoot集成X-admin2.2时遇到的Layui字体图标显示问题。 ... [详细]
author-avatar
卜土杠烟2502932477
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有