如何理解redux中间件(一)

作者: Jmingzi_ | 来源:发表于2017-03-23 19:57 被阅读231次

    “中间件”这个词听起来很恐怖,但它实际一点都不难。想更好的了解中间件的方法就是看一下那些已经实现了的中间件是怎么工作的,然后尝试自己写一个。函数嵌套写法看起来很恐怖,但是大多数你能找到的中间件,代码都不超过十行,但是它们的强大来自于它们的可嵌套组合性。

    这段话我们提取关键词后得出:我们需要了解什么是函数嵌套,中间件就是变态的函数嵌套罢了,函数嵌套大约就是函数式编程的概念,而使用函数式编程必须知道的就是柯里化。

    需要知道的概念

    • 函数式编程
    • 柯里化
    • middleware

    这里从middleware开始说起(函数式编程,柯里化概念请自行去理解),就拿异步actionredux-thunk( 简写版)来说

    export default const thunkMiddleware = 
      ({ dispatch, getState }) =>
            next => 
                 action => 
                       typeof action === ‘function’ ? 
                         action(dispatch, getState) : 
                         next(action);
    
    

    Redux中文文档如是解释:

    ...middlewares (arguments): 遵循 Redux middleware API 的函数。每个 middleware 接受 Store
    dispatch
    getState
    函数作为命名参数,并返回一个函数。该函数会被传入 被称为 next的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store 的 dispatch 方法作为 next 参数,并借此结束调用链。所以,middleware 的函数签名是 ({ getState,dispatch }) => next => action

    其中storedispatchgetState方法是在createStore时通过applyMiddleware注入的。

    next 方法用于维护中间件调用链和dispatch,它返回一个接受 action 对象的柯里化函数,接受的 action 对象可以在中间件中被修改,再传递给下一个被调用的中间件,最终dispatch会使用中间件修改后的action来执行。

    如果得到的action是个函数,就用 dispatchgetState 当作参数来调用它,否则就直接分派给store。

    再来看看我们异步的action写法:

    export const test = (params)=> {
          return (dispatch, getState)=> {
                // do something
                dispatch(otherAction)
          }
    }
    

    到这一步,我们对于为什么要使用redux-thunk中间件有了合理的解释,还是不理解可以参考:redux-tutorial-cn

    创建store的过程

    理解middleware我们从创建store的源头开始,其中createStore最常见的写法:

    const enhancer = compose(
        // 应用中间件到store中
        applyMiddleware(reduxPromiseMiddleware(), funActionThunk, thunk)
    )
    export default const store = createStore(reducer, initialState, enhancer)
    

    从上得知,入口从createStore方法开始,所以来先瞅一眼createStore.js源码

    export default function createStore(reducer, preloadedState, enhancer) {
      if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        enhancer = preloadedState
        preloadedState = undefined
      }
    
      if (typeof enhancer !== 'undefined') {    
        if (typeof enhancer !== 'function') {
          throw new Error('Expected the enhancer to be a function.')
        }
       // 根据我们的传参该函数会在此处返回,将其本身作为参数传递给enhancer函数,并再次调用传递剩余参数
        return enhancer(createStore)(reducer, preloadedState)
      }
    
    ...省略
    

    从上面代码的注释处可以看出,调用了enhancer函数,而enhancer函数由applyMiddleware后得到,所以再来看看applyMiddleware.js的源码:

    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)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
    
        return {
          ...store,
          dispatch
        }
      }
    }
    

    此处看到它返回一个函数,而这个函数刚好接受enhancer函数的参数createStore,而这个createStore就是redux封装的方法,接着返回一个函数,该函数的三个参数和创建store时的createStore方法传的参数一致,只不过在这里enhancer === ‘undefined’,此时正好返回了初始化后的store:

    var store = createStore(reducer, preloadedState, enhancer)
    

    这个store包含4个方法,见createStore源码:

    return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
      }
    

    将dispatch用另外变量保存的目的就是为了接下来将dispatch getState注入给所有的middleware

    chain = middlewares.map(middleware => middleware(middlewareAPI))
    

    然后将多个middleware函数经过compose处理后得到一个函数,这一步形象点说就是将最初原始的dispatch经过一层一层middleware中间件“加工后”重新包装并返回生成终极dispatch和初始化后的store

    进一步

    理解完了源码的流程我们最后再来回顾中间件redux-thunk源码,以此来理解,中间件是如何包装dispatch的,不然我们讨论这些有何意义?

    export default const thunkMiddleware = 
      ({ dispatch, getState }) =>
            next => 
                 action => 
                       typeof action === ‘function’ ? 
                         action(dispatch, getState) : 
                         next(action);
    

    现在我们来看源码是不是很清晰?中间件只能接受这2个参数{ dispatch, getState },因为它就是middlewareAPI

    • next是什么
      中间件返回一个函数,参数是next,然后再联系applyMiddleware源码来看
    dispatch = compose(...chain)(store.dispatch)
    

    到这里我们不得不来看下compose的作用是什么了

    compose 做的只是让你不使用深度右括号的情况下来写深度嵌套的函数 (这里不展开,可自行详细了解).

    我们可以看到最右侧的middleware传入的第一个参数就是store.dispatch,然后以返回的结果作为参数继续传递给下一个中间件,最终返回dispatch。所以,这里的next就是最原始的dispatch,或者经过前面的middleware处理过后的dispatch

    • action是什么?
      上面我们可以看到,最终返回的这个终极dispatch成了我们在action中调用的dispatch,让我们回归到最初的调用:
    export const test = (params)=> {
          return (dispatch, getState)=> {  // 这个dispatch是终极dispatch吗?答案是
                // do something
                dispatch(otherAction). // 这个dispatch是终极版dispatch吗?答案不是,有可能是最原始的,也有可能是前一个middleware处理过后的
          }
    }
    

    这里可以看到,这个action返回一个函数,这个函数接受的参数就是我们创建store时调用createStore方法返回的参数(这里只是选取了2个)。要说明的是,这里的dispatch就是终极版的dispatch,中间件里的action是我们在定义Actionreturn的方法或者dispatchotherAction,所以我们看到例子中的test方法(异步action)返回的是一个函数,redux-thunk源码中判断action是一个函数后就返回了一个函数,这个函数的参数就是return (dispatch, getState)=> {}里的参数,给后面触发action用。

    • middleware是如何处理“加工”dispatch的?
      因为我们前面说过:
    dispatch = compose(...chain)(store.dispatch)
    

    middleware chain经过compose后从右到左执行一层一层处理dispatch,其实这个表述不准,或者我可以这样理解:中间件就是一个闭包?在拿到dispatch后保存在这里,当action被触发后,经过异步的操作后,用这个dispatch触发了需要异步处理结束后再触发的action。(此处理解可能不妥)

    这样一来,也方便我们理解为什么要使用redux-thunk中间件来实现异步action了,因为你可以在dispatch之前做其它操作再触发,感觉就像是闭包的概念,dispatch作为闭包中的变量方法,闭包中含有其它异步操作,直到异步操作完成之后才被触发调用。(这里的闭包概念引用出处:前端基础进阶(六):在chrome开发者工具中观察函数调用栈、作用域链与闭包

    最后,希望也请各位看官大爷有错必究,不要只顾着喷小弟理解不到位的地方,[跪谢]。

    如何理解redux中间件(二) 敬请期待

    相关文章

      网友评论

      本文标题:如何理解redux中间件(一)

      本文链接:https://www.haomeiwen.com/subject/zjdtottx.html