1.redux中间件简介
使用中间件的目的,派发事件的过程中,处理事件执行前的state、装饰事件执行中的dispatch方法(包含处理事件执行中的action)、处理事件执行后的state。
redux-logger模块,打印事件执行前后的state、以及action、执行时间等。
2.redux中间件机制
同koa、express中间件相同,koa、express用于处理请求req、响应res,redux用于装饰dispatch派发事件方法。介于redux中间件处理的是state和dispatch,因此需要将store.getState、store.dispatch作为参数传入中间件当中。实际上,dispatch方法用于触发事件,想在dispatch方法前后添加执行函数,就必须采用函数式编程的方式将dispatch方法作为回调函数传入中间件中,前后经过装饰后,最终返回值为接受action作为参数的函数,同dispatch相同;再交给下一个中间件处理,同样做装饰后,返回接受action作为参数的函数。因此,各中间件装饰dispatch方法的顺序为自右向左,各中间件处理函数的执行顺序为自左向右,类似koa将各中间件的处理函数及dispatch方法结合为一个函数。
// applyMiddleware import compose from './compose' // 使用中间件装饰dispach方法 export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // middleware中间件接受参数为包含getState、dispatch的对象,返回函数,该函数接受上一个中间件返回值作为参数 chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } // compose.js export default function (...funcs) { if (funcs.length === 0) { return arg => arg } // 只有一个中间件时,中间件先接受getState、dispatch作为参数,返回函数,该函数接受dispatch作为函数并装饰,随后又返回函数,接受action作为参数 if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) // 数组的reduceRight方法,自右向左遍历执行首参函数(该函数首参为前一次函数执行的结果),次参为初始化传入首参函数的composed // reduce为自左向右遍历数组,调用首参函数逐次修改最终结果值并返回,供下一个首参函数使用 // redux中使用为middleware中间件数组作为参数funcs,dispatch作为参数args,最末一个中间件装饰dispacth方法后,交给上一个中间件装饰 // 具体功能实现为打印接收到的action和state变化,最终返回值接受action作为参数,完成派发事件的目的 return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
3.redux-logger日志,打印state、action
中间件书写,预先配置中间件,其后接受getState方法用于打印事件触发前后的state(middleware(middlewareAPI)处理过程中),其后接受dispatch或下一个中间件装饰dispatch方法(logger中间件通常位于中间件的下游,因此最有可能直接接受dispatch方法),其后装饰函数接受action触发事件。
使用:
import { applyMiddleware, createStore } from 'redux'; import thunk from 'redux-thunk'; import promise from 'redux-promise'; import createLogger from 'redux-logger'; const logger = createLogger(); const store = createStore( reducer, applyMiddleware(thunk, promise, logger) );
源码:
defaults.js // redux-logger默认配置 export default { level: `log`,// console对象方法名,如log等 logger: console,// 不同平台的console对象 logErrors: true,// 使用try-catch方法装饰dispatch方法或各中间件装饰结果函数 collapsed: undefined,// 打印日志时是否分层输出 predicate: undefined,// 接受getState、action作为参数,返回否值时,表示logger中间件不作任何处理 duration: false,// 事件执行时长 timestamp: true,// 事件执行起始时间 stateTransformer: state => state,// 打印前转化state actionTransformer: action => action,// 打印前转化action对象 errorTransformer: error => error,// 打印前转化error对象 colors: {// 日志输出颜色 title: () => `inherit`, prevState: () => `#9E9E9E`, action: () => `#03A9F4`, nextState: () => `#4CAF50`, error: () => `#F20404`, }, diff: false,// 是否比对前后两个state的差别并打印 diffPredicate: undefined,// 函数式判断是否比对前后两个state的差别并打印 // Deprecated options transformer: undefined,// 被取消的方法,使用stateTransformer代替 }; helpers.js export const repeat = (str, times) => (new Array(times + 1)).join(str); export const pad = (num, maxLength) => repeat(`0`, maxLength - num.toString().length) + num; // 转化日期对象 export const formatTime = (time) => `${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}: ${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`; // H5 performance用于精确反应每段函数的处理时长,效果等同Date对象 export const timer = (typeof performance !== `undefined` && performance !== null) && typeof performance.now === `function` ? performance : Date; index.js import { printBuffer } from './core'; import { timer } from './helpers'; import defaults from './defaults'; // createLogger接受options配置中间件,返回值传入applyMiddleware方法中挂载该中间件 function createLogger(options = {}) { const loggerOptions = { ...defaults, ...options, }; const { logger,// console对象 transformer,// 被取消的方法,使用stateTransformer代替 stateTransformer,// 打印前转化state errorTransformer,// 打印前转化error对象 predicate,// 接受getState、action作为参数,返回否值时,表示logger中间件不作任何处理 logErrors,// 使用try-catch方法装饰dispatch方法或各中间件装饰结果函数 diffPredicate, } = loggerOptions; // 不作任何处理,移交给下一个中间件 if (typeof logger === `undefined`) { return () => next => action => next(action); } if (transformer) { console.error(`Option 'transformer' is deprecated, use 'stateTransformer' instead!`); // eslint-disable-line no-console } const logBuffer = [];// 数组形式存储待打印对象,闭包缓存 return ({ getState }) => (next) => (action) => { if (typeof predicate === `function` && !predicate(getState, action)) { return next(action); } const logEntry = {}; logBuffer.push(logEntry); logEntry.started = timer.now();// 事件开始时间 logEntry.startedTime = new Date(); logEntry.prevState = stateTransformer(getState());// 事件起始state状态,经过stateTransformer转化 logEntry.action = action;// 事件接受的action let returnedValue;// 缓存logger中间件装饰函数 if (logErrors) { try { returnedValue = next(action); } catch (e) { logEntry.error = errorTransformer(e); } } else { returnedValue = next(action); } logEntry.took = timer.now() - logEntry.started;// 事件处理占用的时长 logEntry.nextState = stateTransformer(getState());// 事件结束的state状态,经过stateTransformer转化 const diff = loggerOptions.diff && typeof diffPredicate === `function` ? diffPredicate(getState, action) : loggerOptions.diff; // 打印action日志 printBuffer(logBuffer, { ...loggerOptions, diff }); logBuffer.length = 0;// 释放logBuffer if (logEntry.error) throw logEntry.error; return returnedValue; }; } export default createLogger; core.js import { formatTime } from './helpers'; import diffLogger from './diff'; // 获取打印方法名等 function getLogLevel(level, action, payload, type) { switch (typeof level) { case `object`: return typeof level[type] === `function` ? level[type](...payload) : level[type]; case `function`: return level(action); default: return level; } } // 一组输出文案的标题,对应一个事件,包含action.type,触发时间,执行时长 function defaultTitleFormatter(options) { const { timestamp, duration, } = options; return (action, time, took) => { const parts = [`action`]; if (timestamp) { parts.push(`@ ${time}`); } parts.push(action.type); if (duration) { parts.push(`(in ${took.toFixed(2)} ms)`); } return parts.join(` `); }; } // 打印日志 export function printBuffer(buffer, options) { const { logger, actionTransformer, titleFormatter = defaultTitleFormatter(options),// 获取一组日志文案的标题 collapsed, colors, level, diff, } = options; buffer.forEach((logEntry, key) => { const { started, startedTime, action, prevState, error } = logEntry; let { took, nextState } = logEntry; const nextEntry = buffer[key + 1]; if (nextEntry) { nextState = nextEntry.prevState; took = nextEntry.started - started; } const formattedAction = actionTransformer(action); const isCollapsed = (typeof collapsed === `function`) ? collapsed(() => nextState, action) : collapsed; const formattedTime = formatTime(startedTime); const titleCSS = colors.title ? `color: ${colors.title(formattedAction)};` : null; const title = titleFormatter(formattedAction, formattedTime, took); // console.log("%c text","color:red"); // %c使用css风格设置样式,%s字符串替换,%d或%i数值替换 // %f浮点型替换,%o替换为dom对象,%O替换为对象 // console.log("%c text","color:red","string"); 将string作为最后字符插入 // console.group、console.groupCollapsed分层打印内容,group展开,groupCollapsed折叠 try { if (isCollapsed) { if (colors.title) logger.groupCollapsed(`%c ${title}`, titleCSS); else logger.groupCollapsed(title); } else { if (colors.title) logger.group(`%c ${title}`, titleCSS); else logger.group(title); } } catch (e) { logger.log(title); } const prevStateLevel = getLogLevel(level, formattedAction, [prevState], `prevState`); const actionLevel = getLogLevel(level, formattedAction, [formattedAction], `action`); const errorLevel = getLogLevel(level, formattedAction, [error, prevState], `error`); const nextStateLevel = getLogLevel(level, formattedAction, [nextState], `nextState`); if (prevStateLevel) { if (colors.prevState) logger[prevStateLevel](`%c prev state`, `color: ${colors.prevState(prevState)}; font-weight: bold`, prevState); else logger[prevStateLevel](`prev state`, prevState); } if (actionLevel) { if (colors.action) logger[actionLevel](`%c action`, `color: ${colors.action(formattedAction)}; font-weight: bold`, formattedAction); else logger[actionLevel](`action`, formattedAction); } if (error && errorLevel) { if (colors.error) logger[errorLevel](`%c error`, `color: ${colors.error(error, prevState)}; font-weight: bold`, error); else logger[errorLevel](`error`, error); } if (nextStateLevel) { if (colors.nextState) logger[nextStateLevel](`%c next state`, `color: ${colors.nextState(nextState)}; font-weight: bold`, nextState); else logger[nextStateLevel](`next state`, nextState); } // 比对前后两个state的差别并打印 if (diff) { diffLogger(prevState, nextState, logger, isCollapsed); } try { logger.groupEnd(); } catch (e) { logger.log(`—— log end ——`); } }); } diff.js // differ(objA,objB)比较objA、objB两对象,输出为改变状况 import differ from 'deep-diff'; // https://github.com/flitbit/diff#differences const dictionary = { 'E': { color: `#2196F3`, text: `CHANGED:`, }, 'N': { color: `#4CAF50`, text: `ADDED:`, }, 'D': { color: `#F44336`, text: `DELETED:`, }, 'A': { color: `#2196F3`, text: `ARRAY:`, }, }; function style(kind) { return `color: ${dictionary[kind].color}; font-weight: bold`; } function render(diff) { const { kind, path, lhs, rhs, index, item } = diff; switch (kind) { case `E`: return `${path.join(`.`)} ${lhs} → ${rhs}`; case `N`: return `${path.join(`.`)} ${rhs}`; case `D`: return `${path.join(`.`)}`; case `A`: return [`${path.join(`.`)}[${index}]`, item]; default: return null; } } // 比对前后两个state的差别并打印 export default function diffLogger(prevState, newState, logger, isCollapsed) { const diff = differ(prevState, newState); try { if (isCollapsed) { logger.groupCollapsed(`diff`); } else { logger.group(`diff`); } } catch (e) { logger.log(`diff`); } if (diff) { diff.forEach((elem) => { const { kind } = elem; const output = render(elem); logger.log(`%c ${dictionary[kind].text}`, style(kind), output); }); } else { logger.log(`—— no diff ——`); } try { logger.groupEnd(); } catch (e) { logger.log(`—— diff end —— `); } }
3.redux-thunk函数式编程,将dispatch方法作为参数传入,用于触发另一个异步事件
与redux-logger大迥异的是,redux-logger通过向dispatch传入action触发实际事件,redux-thunk接受的是函数,第一时间并未触发事件,只是在装饰过程中,函数体内又获得store.dispatch方法,用于触发异步事件,除此以外,均为待执行函数,与redux式的事件机制无关,返回值也与redux无关,由thunk中间件传参函数构造,若为promise对象,可以链式调用promise方法。
使用:https://github.com/gaearon/redux-thunk
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers'; // Note: this API requires redux@>=3.1.0 const store = createStore( rootReducer, applyMiddleware(thunk) ); function fetchSecretSauce() { return fetch('https://www.google.com/search?q=secret+sauce'); } // These are the normal action creators you have seen so far. // The actions they return can be dispatched without any middleware. // However, they only express “facts” and not the “async flow”. function makeASandwich(forPerson, secretSauce) { return { type: 'MAKE_SANDWICH', forPerson, secretSauce }; } function apologize(fromPerson, toPerson, error) { return { type: 'APOLOGIZE', fromPerson, toPerson, error }; } function withdrawMoney(amount) { return { type: 'WITHDRAW', amount }; } // Even without middleware, you can dispatch an action: store.dispatch(withdrawMoney(100)); // But what do you do when you need to start an asynchronous action, // such as an API call, or a router transition? // Meet thunks. // A thunk is a function that returns a function. // This is a thunk. function makeASandwichWithSecretSauce(forPerson) { // Invert control! // Return a function that accepts `dispatch` so we can dispatch later. // Thunk middleware knows how to turn thunk async actions into actions. return function (dispatch) { return fetchSecretSauce().then( sauce => dispatch(makeASandwich(forPerson, sauce)), error => dispatch(apologize('The Sandwich Shop', forPerson, error)) ); }; } // Thunk middleware lets me dispatch thunk async actions // as if they were actions! store.dispatch( makeASandwichWithSecretSauce('Me') ); // It even takes care to return the thunk’s return value // from the dispatch, so I can chain Promises as long as I return them. store.dispatch( makeASandwichWithSecretSauce('My wife') ).then(() => { console.log('Done!'); });
源码:
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;
相关推荐
本项目涉及的主题是“一个简单的Redux中间件及Redux API”,这将涵盖Redux的基本概念、中间件的工作原理以及如何自定义中间件来增强Redux的功能。 首先,Redux的核心理念是单向数据流。所有状态都保存在一个单一的...
`react-redux`和Redux中间件在其中扮演着关键角色,特别是在构建大型、复杂的应用时。本文将深入探讨如何使用Redux中间件来直观地将URL映射到状态,以及在React应用中如何实现这一功能。 首先,让我们理解`react-...
Redux中间件,用于处理与和完全兼容的全局加载 用法 Redux加载中间件为每个未解决的返回承诺的动作设置了加载状态,在承诺解析后,它将其加载状态设置为false。 此外,它还链接了在200ms阈值内运行的未解决的动作...
redux 中间件。允许用户使用 Promise 函数进行异步处理。与 redux-promise 不同的是,redux-promise 是处理 action 或 action.payload 为 Promise 的情况,而 redux-ra-promise 是处理当响应函数为 Promise 的情况 ...
Redux 中间件的使用详解 Redux 中间件是 Redux 框架中的一个重要组件,它提供了处理异步请求的能力,增强了 Redux 的功能。本文将详细介绍 Redux 中间件的使用,包括中间件的基本原理、种类、使用场景和实现方式。 ...
redux-websocket是用于通过WebSocket连接管理数据的Redux中间件。 该中间件使用动作与WebSocket连接进行交互,包括连接,断开连接,发送消息和接收消息。 所有动作均遵循模型。 产品特点 用TypeScript编写。 通过...
还原操作概述redux-undo-actions是用于撤消/重做操作的Redux中间件。 它不会将状态更改为 。 而不是更改应用程序的状态,而是调度与历史记录相反的操作。安装要开始使用redux-undo-actions,您需要运行以下命令: ...
本文将深入探讨Redux中间件的实践及其重要性。 首先,我们来理解为什么需要使用Redux中间件。在Redux的基本流程中,当用户在用户界面(UI)触发一个操作时,这通常会生成一个行动(Action)对象,该对象通过`store....
在本文中,我们将深入探讨React-Redux库的核心概念,并尝试自己动手实现一个迷你Redux,同时了解如何使用`react-redux-thunk`中间件。Redux作为JavaScript状态管理库,它简化了React应用中的数据流管理,而`react-...
使用Redux中间件,以便任何改变导航状态的事件都能正确触发React Navigation的事件监听器。 最后,Reducer使React Navigation动作可以改变Redux状态。动机同时使用Redux和React Navigation的大多数项目不需要此库
- 与其他中间件兼容:`redux-thunk`与大多数其他Redux中间件兼容,可以灵活组合使用。 5. 更进一步 如果你需要更高级的异步控制流,可以考虑使用`redux-saga`或`redux-loop`等中间件,它们提供了更多的控制流工具...
Redux中间件提取发送API请求的异步行为。 用法 开始使用 创建中间件并放入您的中间件链中: import { createStore , applyMiddleware } from 'redux' import createApiMiddleman from 'redux-api-middleman' let ...
使用提取的模块化Redux中间件 目录 入门 使用npm i redux-modular-fetch-middleware下载redux-modular-fetch-middleware npm i redux-modular-fetch-middleware或yarn add redux-modular-fetch-middleware 使用...
Redux中间件生成器,允许将依赖项注入到动作创建者中。安装npm install redux-inject用法示例(ES6 / ES7) import { applyMiddleware , createStore } from 'redux' ;import inject from 'redux-inject' ;import ...
简单而强大的redux中间件,支持异步副作用(以及更多) Redux中间件将使您能够: 创建具有副作用的动作,该动作将针对不同的副作用结果分配不同的动作 创建具有异步和同步副作用的动作 向您的异步操作添加挂钩(回...
本文将深入探讨React Redux中间件的原理、作用以及如何实际应用。Redux中间件提供了一种方式,使得我们可以拦截并定制Redux的dispatch动作流程,从而扩展其功能。在"redux-middleware-master"这个压缩包中,可能包含...
Redux在Redux中间件上的应用 项目结构 / src - Components - PostList.js - reducers - index.js - actions - index.js - apis - json.js 应用步骤 (1)安装必需的库[redux,react-redux,axios,redux-thunk]...
redux-axios-中间件 Redux中间件,用于使用axios HTTP客户端获取数据 安装 npm i -S redux-axios-...默认情况下,您只需要从包中导入中间件并将其添加到redux中间件中,并使用第一个参数与axios实例一起执行即可。 第
redux-auth 用于基于JWT的授权的简单Redux中间件可与任何异步存储中间件一起使用。笔记: 该项目正在进行中,目前处于非常早期的阶段。 即将发布的更新以及未来功能的总体路线图。如何使用: 在创建商店的地方添加...
一个简单的redux中间件,如果分派返回的状态等于分派之前的状态,它将在控制台中记录错误。 安装 npm install redux-unhandled-action --save API 有一个参数,一个可选的回调,如果未处理动作,则将调用该回调。...