redux中间件

一、中间件的概念

redux是有流程的,那么,我们该把这个异步操作放在哪个环节比较合适呢?

  • Reducer?纯函数只承担计算State功能,不适合其它功能。
  • View?与State一一对应,可以看做是State的视觉层,也不适合承担其它功能。
  • Action?它是一个对象,即存储动作的载体,只能被操作。

其实,也只有dispatch能胜任此重任了。那么怎么在dispatch中添加其它操作呢?

let next = store.dispatch;
store.dispatch = function(action){
   console.log('老状态 ',store.getState());
   next(action);
   console.log('新状态 ',store.getState());
}
复制代码

示例中可以看出,我们对store.dispatch重新进行了定义,在发送action的前后,做了打印。

这是中间件的大致雏形,真实的中间件要比这么复杂多了

二、中间件的用法

我们在这里先看看中间件是怎么使用,下面我们一步步剖析每个细节。

import {applyMiddleware,createStore} from 'redux';
import reduxLogger form 'redux-logger';

const store = createStore(reducer,inital_state,applyMiddleware(thunk, promise,reduxLogger));

复制代码

代码中有两点需要注意:

  • 1、createStore方法可以整个应用的初始状态作为参数 内部是这么处理的
let state = inital_state;
复制代码
  • 2、中间件的参数次序有讲究。下面我会把这个问题讲明白。

三、applyMiddleware

Middleware可以让你包装storedispatch方法来达到你想要的目的。同时,middleWare还拥有“可组合”这一关键特性。多个middleWare可以被组合到一起使用,形成middleWare链,依次执行。其中每个middleware不需要关心链前后的的middleWare的任何信息。

function applyMiddleware(...middlewares){
    return function(createStore){
        return function(reducer){
            //引入store
            let store = createStore(reducer);
            let dispatch = store.dispatch;
            let middlewareAPI = {
                getState:store.getState,
                // 对dispatch进行包装
                dispatch:action=>dispatch(action)
            }
            //每个中间件都是这种模型  ({ getState, dispatch }) => next => action
            chain = middlewares.map(middleware=>middleware(middleAPI));
            dispatch = compose(...chain)(store.dispatch);
            // dispatch被改装后,返回store
            return{...store,dispatch};
        }
    }
}
复制代码

上面代码中,所有中间件都被放进了一个数组chain,然后嵌套执行,最后执行store.dispatch。中间件内部middlewaAPI可以拿到getStatedispatch这两个方法。

...middleware:遵循Redux middleware API的函数。每个middleware接受StoredispatchgetState函数作为命名参数,并返回一个函数。该函数会被传入成为next的下一个middleWare 的dispatch方法,并返回一个接收action的新函数,这个函数可以直接调用next(action),或者在其他需要的时刻调用,甚至根本不去调用它。

所以,接下来,我们就能看到middleware的函数签名是({ getState, dispatch }) => next => action

其实,它的本质就是包装sotre中的dispatch

上面代码中,还用到了compose方法,我们来看看compose是怎么是实现的?

compose

先看下面一个栗子:

function add1(str){
   return str+1;
}
function add2(str){
    return str+2;
 }
 function add3(str){
    return str+3;
 }
 let result = add3(add2(add1('好吃')));// 好吃123;

复制代码

这中写法调用起来,一层套一层,是不是看着很不爽,我们简化一下:

function compose(...fns){
    if(fns.length==1)
     return fns[0];
   return function(...args){
    let last = fns.pop();
    return fns.reduceRight((prev,next)=>{
         return  next(prev);  
    },last(...args));
   }
 }
 let add = compose(add3,add2,add1);//
 let result = add('好吃');// 好吃123
 // 上面的代码其实就是redux3.6.0版本中compose的实现方式
复制代码

看看这个代码是不是用起来,很干练一些。其实还可以简化

 function compose(...fns){
  if(fns.length==1)
     return fns[0];
   return fns.reduce((a,b)=>(...args)=>a(b(...args)));//add3(add2(add1('好吃')))
 }
 let add = compose(add3,add2,add1);//
 let result = add('好吃');// 好吃123
 // 这是redux3.6.0版本之后的compose实现方式,一直沿用至今。
复制代码

至于为什么applyMiddleWare的参数有顺序,这里给出了答案。

四、Applymiddleware的三个常用参数

4.1、日志记录

使用 Redux 的一个益处就是它让 state 的变化过程变的可预知和透明。每当一个 action 发起完成后,新的 state 就会被计算并保存下来。State 不能被自身修改,只能由特定的 action 引起变化。

试想一下,当我们的应用中每一个 action 被发起以及每次新的 state 被计算完成时都将它们记录下来,岂不是很好?当程序出现问题时,我们可以通过查阅日志找出是哪个 action 导致了 state 不正确。

图片的效果是不是很期待啊!!!

我们先来手动实现一版。

// 记录所有被发起的action和新的state
let next = store.dispatch;
store.dispatch = function(action){
   console.log('老状态 ',store.getState());
   next(action);
   console.log('新状态 ',store.getState());
}
复制代码

还是上面的示例,我们来做个修改

let logger = function({ getState, dispatch }){
   return function(next){// 这里的next可以理解为store.dispath,本质上就是调用 middleware 链中下一个 middleware 的 dispatch。
      return function(action){
        console.log('老状态1 ',getState());
        next(action);//派发动作
        console.log('新状态1 ',getState());
    }
    }
}
// 高逼格写法
let logger = ({ getState, dispatch }) => next => action => {
  console.log('老状态1 ',getState());
  next(action)
  console.log('新状态1 ',getState());
}
复制代码

4.2、redux-thunk 中间件

redux-thunkredux官方文档中用到的异步组件,实质就是一个redux中间件,一个封装表达式的函数,封装的目的就是延迟执行表达式。

redux-thunk是一个通用的解决方案,其核心思想是让action可以变成一个thunk,这样的话,同步情况:dispatch(action),异步情况:dispatch(thunk)

下面是redux-thunk的实现:

let thunk = ({dispatch,getState})=>next=>action=>{
    if(typeof action == 'function'){
        action(dispatch,getState);
    }else{
        next(action);//这里可以理解为dispatch(action),本质上就是调用 middleware 链中下一个 middleware 的 dispatch。
    }
}
复制代码

使用redux-thunk

const store = createStore(  
  reducer,
  applyMiddleware(thunk)
);
复制代码

然后我们实现一个thunkActionCreator

    //过一秒加1
    export function thunkActionCreator(payload){
        return function(dispatch,getState){
            setTimeout(function(){
                dispatch({type:types.INCREMENT,payload:payload});
            },1000);
        }
    },
复制代码

最后,在组件中dispatch thunk

this.dispatch(thunkActionCreator(payload));
复制代码

4.3、redux-promise

redux-promise也是延迟执行的表达式,它是解决异步的另外一种方案。

redux-thunk和核心思想是把action变成thunk,而redux-promise的核心思想是让action返回一个promise对象。

这个中间件使得store.dispatch方法可以接收Promise对象作为参数。这时 ,action 有两种写法:

写法一、返回值是一个Promise对象。

function promiseIncrement(payload){
 //  return {type:types.INCREMENT,payload:payload}  以前是这种写法
    return new Promise(function(resolve,reject){
      setTimeout(function(){
        resolve({type:types.INCREMENT,payload:payload});
      },1000);
    });
 },
复制代码

写法二,action 对象的payload属性是一个Promise对象,这需要从

function payloadIncrement(){
    return {
        type:types.INCREMENT,
        payload: new Promise(function(resolve,reject){
            setTimeout(function(){
                if(Math.random()>.5){
                    resolve(100);
                }else{
                    reject(-100);
                }
            },1000)
        })
    }
}
复制代码

下面我们来看看 redux-promise是怎么实现的,就会明白它内部是怎么操作的.

let promise = ({dispatch,getState})=>next=>action=>{
    if(action.then && typeof action.then == 'function'){
        action.then(dispatch);
        // 这里的dispatch就是一个函数,dispatch(action){state:reducer(state,action)};
    }else if(action.payload&& action.payload.then&& typeof action.payload.then == 'function'){
        action.payload.then(payload=>dispatch({...action,payload}),payload=>dispatch({...action,payload}));
    }else{
        next(action);
    }
}
复制代码

上面的代码可以看出,如果Action本身就是一个Promise,它resolve以后的值应该是一个Action对象,会被dispatch方法送出action.then(dispatch);如果Action对象的 payload属性是一个Promise对象,那么无论resolvereject,dispatch 方法都会发出Action

需要dubbo视频教程的朋友。加QQ群:957734884领取资料。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容