Redux中间件原理详解

为了解析中间件,先看一下几个中间件是什么样子,怎么用,运行起来的原理是什么?

1、中间件是什么样子的

1.2 thunk中间件

function createThunkMiddleware(extraArgument) {

  return ({ dispatch, getState }) => next => action => {
    // 如果是函数,就执行函数
    if (typeof action === 'function') {
        return action(dispatch, getState, extraArgument);
    }
    // 如果不是,执行下一个中间件
    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

1.2promise中间件

import isPromise from 'is-promise';
import { isFSA } from 'flux-standard-action';

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    if (!isFSA(action)) {
      return isPromise(action) ? action.then(dispatch) : next(action);
    }

    return isPromise(action.payload)
      ? action.payload
          .then(result => dispatch({ ...action, payload: result }))
          .catch(error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          })
      : next(action);
  };
}

1.3logger中间件

const defaultLogger = ({ dispatch, getState } = {}) => {
  if (typeof dispatch === 'function' || typeof getState === 'function') {
    return createLogger()({ dispatch, getState });
  }
};
function createLogger(options = {}) {
  const loggerOptions = Object.assign({}, defaults, options);

  const {
    logger,
    stateTransformer,
    errorTransformer,
    predicate,
    logErrors,
    diffPredicate,
  } = loggerOptions;

  if (typeof logger === 'undefined') {
    return () => next => action => next(action);
  }

  import { logger } from 'redux-logger'
  const store = createStore(
      reducer,
      applyMiddleware(logger))
    
  import { createLogger } from 'redux-logger'
  const logger = createLogger({
      // ...options
    });
  const store = createStore(
      reducer,
      applyMiddleware(logger));
      
  return () => next => action => next(action);
  }

  const logBuffer = [];

  return ({ getState }) => next => (action) => {
    if (typeof predicate === 'function' && !predicate(getState, action)) {
      return next(action);
    }
    const logEntry = {};

    logBuffer.push(logEntry);

    logEntry.started = timer.now();
    logEntry.startedTime = new Date();
    logEntry.prevState = stateTransformer(getState());
    logEntry.action = action;

    let returnedValue;
    if (logErrors) {
      try {
        returnedValue = next(action);
      } catch (e) {
        logEntry.error = errorTransformer(e);
      }
    } else {
      returnedValue = next(action);
    }

    logEntry.took = timer.now() - logEntry.started;
    logEntry.nextState = stateTransformer(getState());

    const diff = loggerOptions.diff && typeof diffPredicate === 'function'
      ? diffPredicate(getState, action)
      : loggerOptions.diff;

    printBuffer(logBuffer, Object.assign({}, loggerOptions, { diff }));
    logBuffer.length = 0;

    if (logEntry.error) throw logEntry.error;
    return returnedValue;
  };
}
export { defaults, createLogger, defaultLogger as logger };

export default defaultLogger;

2、怎么使用中间件

const store = createStore(rootReducer, initialState, 
    applyMiddleware(thunk),
    ... ...
);

简单来说,createStore做了这么件事:
目的:根据你传入的reducer和初始状态initialState生成初始化store,并提供了一些列操作的接口,像dispatch等
怎么做的呢?参考Redux-creatStore/compose
本文重点讲解中间件的执行过程和原理

3、中间件运行原理

中间件的执行原理和koa中间件的执行原理类似,但是不是洋葱型的,而是半个洋葱,因为redux是单向执行的,走过去就完事了。
当你应用了中间件,在触发一个action操作的时候,action操作就会经过先经过中间件,最终再形成dispatch(action)。
以其中两个中间件为例,说明下,一个触发一个action动作的时候,代码的执行逻辑。
thunk:是允许dispatch一个函数,而不是一个对象
假如说异步打印一个日志。

3.1 中间件的内部逻辑

const store = createStore(reducer, preloadedState, enchancer);

// 如果没有中间件,正常触发一个action;
// 如果有中间件的时候 creatStore内部的执行逻辑是这样的
// enchancer 就是你应用的中间件,调用applyMiddleware得到的组合中间件

function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
  
    // 该创建的store还是要创建的,只传入了两个参数,没有中间件,得到的是正常的store
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
    // 把getState、dispatch传给中间件
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // 下面方法返回来了一个函数数组,中间件被剥离到
    // next => {}
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 再执行下面的,中间件就被剥离到
    // action => {}
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

// 下面得到的结果是 
// 假设中间件为 a b
// a(b(store.dispatch))
return enchancer(createStore)(reducer, preloadedState);

// 结合上面thunk的源码
({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
        return action(dispatch, getState);
    }
    return next(action);
  };

经过上面的操作后,经过中间件包装后的store是什么样子
假设中间件为 a b c
没有中间件的时候是这样的

store = {
    dispatch,
    ... ...,
    subscribe,
    getState
}

经过中间包装后,store变成了

store = {
    dispatch: a(b((store.dispatch))),
    ... ...,
    subscribe,
    getState
}

总结起来就是给每一个中间件配发了一个原始store的dispatch,中间件函数嵌套执行

3.2 触发一个action时,执行逻辑

假设触发一个异步打印日志的功能

  • 应用中间件
const store = createStore(rootReducer, initialState, 
    applyMiddleware(thunk)
);

经过上面的操作,现在的store应该是

{
    dispatch: action => {
        if (typeof action === 'function') {
            return action(dispatch, getState, extraArgument);
        }
        return next(action);
    },
    ... ...,
    subscribe,
    getState
}
  • action函数

当执行这个logNext的时候,返回一个函数,函数的参数是dispatch和getState两个。

const logNext = () => (dispatch, getState) => {
    setTimeout(
        dispatch({
            type: 'LOG',
            payload: {
                content: '这是一条异步日志'
            }})
        ,5000);
}
  • 执行过程
---->
store.dispatch(logNext())  // 传了一个函数,然后执行一个函数
----> 
(dispatch, getState) => {
    setTimeout(
        dispatch({
            type: 'LOG',
            payload: {
                content: '这是一条异步日志'
            }})
        ,5000);
} 
---->

可以看出来,redux-thunk就是一个封装函数,允许store.dispatch一个函数
如果有多个中间件,执行过程是什么样子的?重点在next(action),next是什么呢?
next就是每一个中间件要做的事情

next => action => {}

明白了么?

附录

compsoe

// compose本身并不改变函数的执行,将函数组合后又返回了一个函数
    function compose(...funcs) {
      if (funcs.length === 0) {
        return arg => arg
      }
      if (funcs.length === 1) {
        return funcs[0]
      }
        return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }

相关推荐