浅谈Redux(之一):Middleware原理

Redux作为目前最火的Flux模式实现之一,它有很多的点值得研究。今天我们首先来看看它的Middleware。

熟悉Express或者koa的朋友对Middleware的概念一定不陌生。例如Express中是这样使用一个中间件的:

var app = express();

app.use(function(req, res, next) {
  console.log('%s %s', req.method, req.url);
  next();
});

app.use中的方法,可以在其后面的http VERB调用之前,对request对象和response对象进行处理,然后通过调用next方法将处理过程转发到下一中间件或者通过返回响应来结束处理过程。(之后有机会的话再写一写NodeExpress)。

我理解的所谓中间件其实就是,通过类似装饰者模式的形式,用代码预处理的方式,保证原本处理问题的函数(或方法)调用不变。

Redux中的中间件可以使得在用户调用store.dispatch之后,先对参数stateactions进行预处理,再让真正的store.dispatch调用,以确保reducer纯度(函数式编程的概念)不变。

Redux中提供了applyMiddleware方法,它的源码只有十几行,真的是非常精妙。

下面我们就研究一下它的源代码。

<!–more–>

applyMiddleware方法

applyMiddleware(...middlewares){
    
    return next => (reducer, initialState){
       
        var store = next(reducer, initialState),
            dispatch = store.dispatch,
            chain = [],
            middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action)
            };
            
            chain = middlewares.map(middleware =>
                middleware(middlewareAPI));
            
            dispatch = compose(...chain, store.dispatch);
            
            return {
                ...store,
                dispatch
            }
    }
}

这段代码的意思就是,appleMiddleware方法接收一个Middleware列表,以applyMiddleware(middleware1, middleware2, middleware3)的形式调用(参见ES6的rest参数语法),然后再将创建store的方法传入(我想这个原因是Redux不仅仅可以在React中使用,也可以适用于任何Flux模式的框架和库),然后就会发生神奇的事情。

这两次调用(假设:var newCreateSore = applyMiddleware(middleware1, middleware2)(createStore))会产生一个新的创建Store的方法,但是它改造了原本Store的dispatch方法,让这个dispatch可以做原生dispatch不能做的事情,这样我们就可以订制dispatch的行为,从而实现了中间件的概念。

故而,newCreateStore将作为createStore的替代方法,使用newCreateStore会产生带有中间件的store。

在最内层是如何实现中间件的调用的呢?让我们继续研究。

首先我们用传入的next(一个可以创建Store的函数),创建一个原始的store,并且取出其原生的store.dispatch方法和store.getState方法成为一个对象,作为参数传入中间件函数中,让其第一次包装这个类似store的对象,并返回新的函数。

然后我们使用compose函数,将这些包装过后的返回的函数一个接一个的嵌套调用。

这里补充一下compose的概念:

假设有若干函数f1, f2, f3…,compose指的是类似f1(f2(f3(x)))的调用方式,这在函数式编程中很常见。

(这里的compose函数是redux中的一个方法,这里我们不上它的源码,有兴趣的朋友可以直接看源码。)

被嵌套在compose最内层的是原生的store.dispatch方法,这里我们就一层层的将其包装,在中间件函数中,我们可以利用store的其他方法,比如store.dispatchstore.getState,做一些有意思的事情,比如实现一个记录state改变的日志中间件。

中间件函数

从上面的分析中,我们不难写一个符合要求的中间件函数。

首先中间件函数需要接受一个middlewareAPI,如果使用ES6的语法,这里可以看成是接收一个{dispatch, getState}的形式的参数,这样我们就能在内层使用这两个方法。

接收middlewareAPI参数之后,中间件函数返回另一个函数(为方便后面解释,假设返回的函数为dispatch n)。这个函数既然要用于compose,也就是说它接收一个形式为dispatch的函数,对其一层层嵌套(形式为dispatch1(dispatch2(dispatch3(dispatch))))。在其内部我们可以在之前的dispatch调用之前和之后,进行一些逻辑的处理。

写一个简单的记录state日志的中间件如下:

var middlewareLogger = ({getState}) => next => action => {
    console.log(getState());
    next(action);
    console.log(getState());
}

怎么样,是不是特别简单?

再写一个异步操作的中间件:

const readyStatePromise = store => next => action => {
  if (!action.promise) {
    return next(action)
  }

  function makeAction(ready, data) {
    let newAction = Object.assign({}, action, { ready }, data)
    delete newAction.promise
    return newAction
  }

  next(makeAction(false))
  return action.promise.then(
    result => next(makeAction(true, { result })),
    error => next(makeAction(true, { error }))
  )
}

这个中间件让你可以发起带有一个 { promise } 属性的特殊 action。这个 middleware 会在开始时发起一个 action,并在这个 promise resolve 时发起另一个成功(或失败)的 action。为了方便起见,dispatch 会返回这个 promise 让调用者可以等待。

结束

    原文作者:samchowgo
    原文地址: https://segmentfault.com/a/1190000006149647
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞