从源码理解Redux和Koa2的中间件机制

Redux和Koa的中间件机制相关源码都很精简。redux

正文我将直接引用部分源码,并加以注释来帮助咱们更清晰的理解中间件机制。api

Reudx

redux的中间件机制在源码中主要涉及两个模块数组

内部的compose组合函数

'redux/src/compose.js'

//compose组合函数,接收一组函数参数返回一个组合函数
//须要提早注意的一点是,funcs数组内的函数基本上(被注入了api)就是咱们在将来添加的中间件如logger,thunk`等
export default function compose(...funcs) {
//为了保证输出的一致性,始终返回一个函数
  if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
 //这一步可能有些抽象,但代码极其精致,经过归约函数处理数组,最终返回一个逐层自调用的组合函数。
 //例: compose(f, g, h) 返回 (...args) => f(g(h(...args))).
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

//或许是版本更新的缘故,相比以前看到过的compose要精简了不少,尤为是在最终的规约函数处理上,高大上了很多。
//由本来的reduce来依次执行中间件进化为函数自调用,更加的【函数式】。。下面顺便贴出多是旧的compose函数,你们自行对比。

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
复制代码

咱们添加中间件时最经常使用到的redux提供的applyMiddleware函数

'redux/src/applyMiddleware.js'

//能够看到compose函数被做为🔧引入了
import compose from './compose'
//暴露给开发者,用来传入中间件
export default function applyMiddleware(...middlewares) {
    //createStore函数的控制权将被转让给applyMiddleware,因为本篇主要谈中间件,就不扩展来解释了
  return createStore => (...args) => {
//------------------------------------------------------------------非中间件相关,一些上下文环境的代码-----------------------
    //初始化store,此处的...args实际为reducer, preloadedState(可选)
    const store = createStore(...args)
    //声明一个零时的dispatch函数,注意这里的let,它将在构建完毕后被替换
    let dispatch = () => {
     throw new Error('dispatch不容许在构建中间件的时候被调用,其实主要是为了防止用户自定义的中间件在初始化的时候调用dispatch。 在下文的示例中能够看到, 而且普通的同步的中间件通常是用不到dispatch的')
    }
    //提供给中间件函数的api,能够看到dispatch函数在这里经过函数来'动态的调用当前环境下的dispatch'
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    //为中间件注入api
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
//------------------------------------------------------------------------------------------------------------------------
    
    '关键'
    //列出上文的例子可能比较直观: 调用compose(f, g, h)返回(...args) => f(g(h(...args))).
    //1.调用compose函数,返回一个由多个/一个中间件构成的组合函数
    //2.将store.dispatch做为参数传入组合函数中用来返回一个新的/包装过的dispatch函数
    //'注意:这部分须要联系下文中的中间件源码来对照着进行理解,因此让咱们暂时把这里加入脑内缓存'
    dispatch = compose(...chain)(store.dispatch)
    
    //返回一个store对象,在添加了中间件的状况下,咱们实际最终获取的store就是从这里拿到的。
    return {
      ...store,
      dispatch
    }
  }
}
复制代码

第三方中间件

原本不想写这么长来着,但但愿更多你们可以更简单的理解,就多贴了些源码,毕竟代码远比文字更好理解,下面我用logger和thunk的源码(简化)来作承接上文的简要分析。promise

'redux-logger'
//因为logger源码看起来好像有点复杂(懒得看),我就简单实现了...不严谨请轻喷

一般来讲redux的中间件主要分为两层。

//第一层,用于接受store提供的API,在传给构建中间件以前就会被调用。
const logger = ({getState}) => {
    // 第二层,利用了函数(currying)柯理化将计算/运行延迟,请让我用更多的注释来帮咱们理清思路...
    // 仍是先列出上文的例子比较直观【手动滑稽】:)
    // 例:compose(f, g, h)返回(...args) => f(g(h(...args))). 
    // 关联上文:dispatch = compose(...chain)(store.dispatch) 代入例子 ((dispatch) => f(g(h(dispatch))(store.dispatch)
    // 能够看到清晰看到,中间件被自右向左执行,store.dispatch做为参数被传入给最早执行(最右侧)的中间件
    // 中间件的第二层被执行,返回一个'接受action做为参数的函数',这个函数做为调用下一个(本身左侧)的中间件,依次执行至最左侧,最终返回的一样是一个'接受action的函数'
    // 最终咱们调用的dispatch实际上就是这个被最终返回的函数
    // '咱们的真实流程是 dispatch(包装过的) => 中间件1 => 中间件2 => dispatch(store提供的) => 中间件2 => 中间件1 => 赋值(若是有返回的话)'
    // 果真仍是没有解释清楚,请抛开个人注释,多看几遍代码
    return next => action => {
        console.log('action', action)
        console.log('pre state', getState())
        //next实质就是下一个(右侧)中间件返回的闭包函数/当前中间件若是是最后一个或者惟一的,那么next就是store提供的dispatch
        //next(action)函数调用栈继续往下走,也就是调用下一个(右侧)中间件,nextVal会接受返回的结果
        const nextVal = next(action)
        console.log('next state', getState())
        //将结果返回给上一个中间件(左侧)或者是开发者(第一个中间件的状况下)
        return nextVal
    }
}


'redux-thunk'
//这个是官方的源码,异常精简,这个函数支持了dispatch的异步操做,让咱们来看看如何实现的。
//这里就不复述上面的注释了,只解释下关于异步的支持。
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
        //将dispatch函数的执行权限转移给开发者,咱们一般在异步结束以后调用dispatch(此时是同步的)。
        //注意:在这里咱们本来的中间件执行流程被中断,并从新以同步的模式执行了一遍,'因此redux-thunk在中间件中的位置将会对其他中间件形成影响,例如logger中间件被执行了两次什么的...'
        // 另外一个要注意的是,这里的dispatch函数其实是在构筑中间件后被包装的函数。
        return action(dispatch, getState, extraArgument);
    }
    //dispatch同步时,直接将控制权转让给下一个中间件。
    //dispatch异步时,在异步结束后调用的dispatch中,一样将控制权转让给下一个中间件。
    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;

复制代码

小结

最后让咱们梳理总结一下Redux的中间件流程。缓存

  1. 首先是提供一个compsoe函数用来生成一个由多个中间件构成的组合函数(存在自调用能力)
  2. 将store的API注入中间件
  3. 将store.dispath做为参数传递给一个由compose函数构成的组合函数,返回一个包装后的dispatch(也就是咱们真实使用的dispatch)
  4. (或者是0.)构筑一个特定结构的的中间件,第一层用于注入API,第二层用来接受上一个中间件返回的一个接受action做为参数的函数,而且自身一样返回一个包含中间件具体操做的接受action做为参数的函数
  5. 由中间件提供的dispatch被调用,中间件被依次调用,若是遇到提供了异步支持,那么在异步状况下,dispatch会先按照普通流程调用,当遇到redux-thunk或者redux-promise等函数时,会以同步的形式从新调用当前dispatch(中间件也会被从新调用一遍)

下面丢张费了九牛二虎之力画的调用流程图...随意看看就好...bash

Koa2

感受基本没多少朋友看到这里了吧...但我仍是要写完。 同上,先贴源码让代码来告诉咱们真相闭包

在redux里,中间件是做为一个附加的功能存在的,但在koa里中间件是它最主要但机制。app

koa的核心代码被分散在多个独立的库中,首先来看中间件机制核心的compose函数

'koa-compose'

//注意:'在函数中始终返回Promise,是因为koa2采用了async await语法糖形式'
//接受一个中间件数组
function compose (middleware) {
    返回一个处理函数,在Request请求的最后被调用,并传入请求的相关参数
  return function (context, next) {
    let index = -1
    //执行并返回第一个中间件
    return dispatch(0)
    
    一个接受一个数字参数,用来依次调用中间件
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      //在只有一个中间件的时候直接调用自身
      if (i === middleware.length) fn = next
      //中间件被执行完毕了,直接返回一个Promise
      if (!fn) return Promise.resolve()
      try {
        //将下一个中间件的函数调用包裹在next中,返回给当前的中间件
        return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
      } catch (err) {
        //监听错误,并抛出
        return Promise.reject(err)
      }
    }
  }
}

//相比较redux的中间件缺乏了几分函数式的精致,但我依旧写不出相似精简的代码.jpg
复制代码

koa本体

'koa/lib/application.js'

'104-115行的use函数(简化)'

//这是koa暴露给咱们的use函数,相信大多数同窗都不陌生
  use(fn) {
    //很是明了,就是将中间件添加入middleware数组
    this.middleware.push(fn);
    return this;
  }

'125-136行的callback函数'
//callback函数将在koa.listen中被调用具体请自行查看源码

  callback() {
    //调用compsoe
    const fn = compose(this.middleware);
    if (!this.listeners('error').length) this.on('error', this.onerror);
    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      //Request时被调用
      return this.handleRequest(ctx, fn);
    };
    return handleRequest;
  }

复制代码

koa2小结

关于koa2的中间件机制我并无解释多少,主要是因为相比redux中间件来讲简明许多,另外一个缘由主要是懒,具体的执行流程图,实际上一样是洋葱形的,只是store.dispatch被换成了最后一个中间件而已。koa

本篇文章,虽然质量不行,大多注释偏口语化(专业词汇量不足),但仍是但愿可以对一些同窗有所帮助。异步

临渊羡鱼不如退而结网

相关文章
相关标签/搜索