一文全解Redux中间件middleware & applymiddleware 柯里化 redux thunk

Redux核心概念

Redux的核心概念其实很简单:将需要修改的state都存入到store里,发起一个action用来描述发生了什么,用reducers描述action如何改变state tree 。创建store的时候需要传入reducer,真正能改变store中数据的是store.dispatch API。
Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。(功能有限 需要中间件)
Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

Redux中间件

dispatch一个action之后,到达reducer之前,进行一些额外的操作,就需要用到middleware。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
换言之,中间件都是对store.dispatch()的增强。
不难发现:

  1. 不使用middleware时,在dispatch(action)时会执行rootReducer,并根据action的type更新返回相应的state。
  2. 而在使用middleware时,简言之,middleware会将我们当前的action做相应的处理,随后再交付rootReducer执行。
    https://cn.redux.js.org 中文官方文档
    阮一峰

    注意,不太需要研究如何写中间件,因为:常用的中间件都有现成的,只要引用别人写好的模块即可。
    故 只要按照中间件官方提供的规定格式 实现程序,并应用到中间件方法,那么,在执行一个action的时候,就会成功调用中间件。

中间件 的软件工程定义是什么

中间件不是产出最终结果,而是 处理一些 程序流程中间 的操作。
作用:中间件常做一些 业务逻辑之间 的数据转换,让开发者不需要关注底层逻辑,而专心于当前的开发。
Middleware makes it easier for software developers to implement communication and input/output, so they can focus on the specific purpose of their application. -- Wiki
比如 ajax==>json(乱的数据)==>service转换(中间件)==>UI(整齐的数据)

示例:使用redux中间件

applymiddleware将一堆函数封装成一个函数,这些函数的执行顺序由next传递。

import { createStore, applyMiddleware } from 'redux';

// 中间件为 高阶函数:1.函数做参数 2.return出的也只是函数
const logger1 = store => next => action => { //柯里化:next函数 在store的源码中被一番处理后返回(高阶函数),新next再接收参数action
    console.log('current dipatch' + JSON.stringify(action));
    next(action);
};
const logger2 = store => next => action=> {
    next(action);
};
const logger3 = store => next => action=> {
    next(action);
};

const enhancer = applyMiddleware(logger1, logger2, logger3); //enhancer为一个函数,否则报错
const reducer = (state, action) => state;
const store = createStore(reducer, {}, enhancer); //{}为初始化的store数据
store.dispatch({type:1});
store.dispatch({type:2});
store.dispatch({type:3});
//createStore第3个参数enhancer若存在,将先执行enhancer内的中间件逻辑后再dispatch,而后才执行reducer的逻辑。
//可见中间件增强enhance了reducer&createStore

详解JS函数柯里化 - 简书
柯里化:多参函数->单参函数
就是把一个由多个参数的函数,写成 多个函数嵌套,每个函数只有1个参数,每个函数返回下一个函数。

redux不用中间件 的代码书写顺序
action ==> dispatch ==> reducer ==> nextState

redux用中间件 的代码书写顺序 (middleware是在dispatch和reducer之间起作用)
action ==> middleware ==> dispatch ==> reducer ==> nextState

如何自己写一个中间件

在官方的示例中,有一个logger实现的示例

const logger = store => next => action =>{
    console.log('prev state',store.getState()) //获取状态数据
    console.log('dispatch',action);

    let result = next(action);

    console.log('next state',store.getState());

    return result;
}

从这个示例可以看出,实现一个中间件需要做一下几件事情:

  1. 三层函数包装:第一层传入store,第二层传入下一个中间件next,第三层传入此次执行的action;
  2. 在最内层的函数实现中,需要调用next中间件,并返回结果。
    参考 https://www.jianshu.com/p/dbe65d95c77b

applyMiddleware与redux中间件的原理

封装改造store.dispatch,将其指向中间件,以实现 在dispatch和reducer之间 处理action数据的逻辑。
大体原理如下

// 储存原来的dispatch
const dispatch = store.dispatch;

// 改变dispatch指向
store.dispatch = dispatchPre; //指向中间件

// 重命名
const next = dispatch;

参考 源码分析 https://segmentfault.com/a/1190000018347235

redux-thunk中间件+applymiddleware 实现 能异步的reducer

Redux store 默认仅支持同步数据流。 使用 thunk中间件 可以帮助在 Redux 应用中实现 异步的reducer。比如,action中 有setTimeout、ajax/fetch调用远程API 这些场景,就可用thunk中间件。
可以将 thunk中间件 看做 store 的 dispatch() 方法的封装器。我们可以使用 thunk的"action creator结构函数"来派遣 函数或Promise,而不是返回 action 对象,,具体如下。
背景: 没有 thunk 的话,默认地是同步派遣。虽然,同步机制 依然可以从React组件发出API调用(例如使用componentDidMount()生命周期方法发出这些请求),但在Redux应用中,同步派遣 难以实现以下两点:1.可重用性(思考下合成) 2.可预测性,只有 action creator 可以是状态更新的单一数据源。

直接将thunk中间件引入,放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。即可以在reducer中进行一些异步的操作。

import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import reeducers from '....';
const store = createStore(
  reducers, 
  applyMiddleware(thunk, logger) //thunk本身是一个中间件,logger也是一个中间件,还可以放更多
); //thunk的实例被传递给Redux的applyMiddleware() enhancer函数

其实applyMiddleware就是Redux的一个原生方法,将所有中间件组成一个数组,依次执行。
中间件多了可以当做参数依次传进applyMiddleware。

Redux-thunk可以让reducer直接使用 异步操作,通过把 异步的action creator 放入reducer中。
如下就是个action creator,它必须返回一个函数(而非对象),可以是异步函数

export function addCount() {
  return {type: ADD_COUNT}
}
export function addCountAsync() { 
  return dispatch => {
    setTimeout( () => {
      dispatch(addCount())
    },2000)
  }
}

addCountAsync函数就返回了一个函数,将dispatch作为函数的第一个参数传递进去,在函数内进行异步操作就可以了。

该部分参考如下
redux-thunk的源码分析和更多操作 见
Redux中间件之redux-thunk使用详解 - 简书 源码分析
Redux-thunk中间件 - 简书 使用方法 如何写异步ajax的fetch+promise接收数据

全文参考

阮一峰 -- Redux 入门教程(二):中间件与异步操作 (涉及redux-thunk redux-promise) https://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html

推荐阅读更多精彩内容

  • 用过react的同学都知道在redux的存在,redux就是一种前端用来存储数据的仓库,并对改仓库进行增删改查操作...
    这个前端不太冷阅读 2,545评论 1 6
  • 思考 我们在 Redux 异步 Action 中经常使用这种写法: 而正常的 Action 就看起来好像应该是这样...
    7revor阅读 345评论 0 0
  • 前言 最近几天对 redux 的中间件进行了一番梳理,又看了 redux-saga 的文档,和 redux-thu...
    pansly阅读 332评论 0 1
  • 前言 最近几天对 redux 的中间件进行了一番梳理,又看了 redux-saga 的文档,和 redux-thu...
    Srtian阅读 31,740评论 9 40
  • 一、中间件的概念 为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加? (1)R...
    丹蕾_7933阅读 2,435评论 0 2
  • 以前家里用的都是棉花被,但是现在,大家家里用的都是蚕丝被,虽然品种繁多,但却是最受欢迎的。蚕丝被盖在身上相对于棉花...
    宜州后生阅读 3,542评论 0 0
  • 153 梦泽悲风动白茅,楚王葬尽满城娇。 未知歌舞能多少,虚减宫厨为细腰。---- 唐代 · 李商隐《梦泽》 15...
    郭零阅读 215评论 0 1
  • 我这么懒,又这么笨,该学点什么好呢? 前天刚过完生日,正式跨入了三十岁大关咯!又不敢随便嫁人,因为自己一无是处啊!...
    晴天尼尼阅读 1,479评论 39 12
  • 从梦中惊醒 你还是走了 表现的再是无所谓 心碎瞒不住自己 任何事都经不起推敲 表面风轻云淡 一件件揉碎了看 恨不得...
    尘世尘子阅读 56评论 0 1
  • 1此刻我写着一天的总结,娃爸在给抹脚丫,这种生活中的小事儿,只要有要求,他都会很主动去做,被宠爱,确实很幸福的感觉...
    四月龄宝妈月月阅读 125评论 0 0