middleware 的演进史:为什么我们需要Middleware

参考:http://www.redux.org.cn/docs/advanced/Middleware.html

在Express或者Koa等服务端框架中,middleware 是指可以嵌入在框架接受请求到产生相应过程之中的代码。例如,Express、Koa 的 middleware 可以完成添加 CROS headers 、记录日志、内容压缩等工作。middleware 最优秀的特性就是可以被链式组合

Redux 中的middleware被用于解决不同的问题,但其中的概念是类似的。他提供的是位于action被发起之后,到达reducer之前的扩展点

演进1:实现记录每一次action及之后的state

//monkeypatching
//用 next 变量来保存原有的store.dispatch
//然后将store.dispatch用带有定制功能的dispatchAndLog替代
//dispatchAndLog 中最终用next(即原始的store.dispatch)派发action
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action){
    console.log('dispatching',action);
    let result = next(action);
    console.log('next state' ,store.getState())
    return result;
}

演进1基本实现了记录action和state的功能。但有一个问题,当我们需要添加崩溃报告的时候(即不仅要记录每一次action和state,当出现错误时及时报告)怎么办?

演进2:增加崩溃报告

日志记录和崩溃报告分离是很重要的。理性情况下我们希望它们是两个不同的模块,也可能在不同的包中。

function patchStoreToAddLogging(store){
    let next = store.dispatch;
    store.dispatch = function dispatchAndLog(action){
        console.log('dispatching',action);
        let result = next(action);
        console.log('next state' ,store.getState())
        return result;
    }
}

function patchStoreToAddReporting(store){
    let next = store.dispatch;
    store.dispatch = function dispatchAndReportErrors(action){
        try{
            return next(action);
        }catch(err){
            console.log('捕获异常:',err);
            Raven.captureException(err,{
                extra:{
                    action,
                    state:store.getState()
                }
            });
            throw err;
        }
    }
}

使用:

patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)

这样实质上是完成了一个功能上的链式串联:

当上述两个函数执行完成之后,store.dispatch 为 dispatchAndReportErrors;next 为dispatchAndLog。因而最终在应用中调用dispatch时,实际上是执行的一串链式调用:dispatchAndReportErrors -> dispatchAndLog ->(origin) dispatch.

图片来源:https://segmentfault.com/a/1190000012653724

从上也可以看出middleware的调用有一定的顺序性。

演进2可以看出使用时总是需手动调用两个函数,麻烦。

演进3:返回dispatch函数

function logger(store){
    let next = store.dispatch;
    return function dispatchAndLog(action){
        console.log('dispatching',action);
        let result = next(action);
        console.log('next state' ,store.getState())
        return result;
    }
}
function applyMiddlewareByMonkeypatching(store,middlewares){
    middlewares = middlewares.slice();
    middlewares.reverse();
    middlewares.forEach(middleware=>
        //替换掉原始的dispatch
        store.dispatch = middleware(store)
    )
}

使用:

applyMiddlewareByMonkeypatching(store,[logger,crashReporter]);

演进4:以上每次改变 store.dispatch 的赋值实质上是为了实现链式化调用,可以不使用 store.dispatch 作中间桥梁,而直接通过嵌套函数来解决。

const logger = store => next => action => {
    console.log('dispatching',action);
    let result = next(action);
    console.log('next state',store.getStore());
    return result;
}

const crashReporter = store => next => action =>{
    try{
        return next(action);
    }catch(err){
        console.error('Caught an exception!',err);
        Raven.captureException(err,{
            extra:{
                action,
                state:state.getState()
                }
        })
        throw err;
    }
}
//注意:这不是redux applyMiddleware的实现
//但是非常接近
function applyMiddleware(store,middlewares){
    middlewares = middlewares.slice();
    middlewares.reverse();

    let dispatch = store.dispatch;

    middlewares.forEach(middleware =>
        dispatch = middleware(store)(dispatch);
    )

    return Object.assign({},store,{dispatch});
}

Middleware 接收了一个next()的 dispatch 函数,并返回一个 dispatch 函数,返回的函数会被作为下一个 middleware 的next(),以此类推。由于 store 中类似getState()的方法依旧非常有用,我们将store作为顶层的参数,使得它可以在所有 middleware 中被使用。

演进5:redux 的Middleware

演进4中我们写的两个Middleware可以在redux中这么使用:

import {createStore,combineReducers,applyMiddleWare} from 'redux';

let reducer = combineReducers(reducers);

let store = createStore(
    reducer,
    applyMiddleware(logger,crashReporter)
)

中间件的特点

  • 中间件是独立的函数:中间件之间不应该有依赖关系,每个中间件应该能够独立被一个Redux Store 使用,完成某一特定功能。
  • 中间件可以组合使用
  • 中间件有一个统一的接口
//一个什么都不做的中间件
function doNothingMiddleware({dispatch,getState}){
    return function(next){
        return function(action){
            return next(action);
        }
    }
}

//使用es6表示法看起来更舒服一些
const doNothingMiddleware = store => next => action => {
    next(action);
}

参见doNothingMidddleware中间件,为什么一个中间件的接口定义需要这么多层函数能?(外层包裹+内3层return)。

Redux是根据函数式编程思想来设计的,函数式编程的一个重要思想就是让每个函数的工能尽可能的小,然后通过函数的嵌套组合来实现复杂的功能

results matching ""

    No results matching ""