Redux中间件原理

Published on
发布于·预估阅读11分钟
    Authors
    • Name
      willson-wang
      Twitter

    redux 的 middleware 是为了增强 dispatch 而出现的

    没有middleware image

    有middleware image

    那么redux中间件是怎样实现的?

    我首先想到的是redux-thunk,因为这个最简单,也最好理解,它是怎么做到支持传入disptach方法的参数可以是函数,然后看了下源码实现,只有短短几行代码,如下所示

    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;
    

    实现逻辑就是对传入的action做判断,如果传入的action是函数,则调用action,把dispatch、getState在传入到action函数内,方便action函数内处理完副作用之后,可以执行dispatch,也可以通过getState获取到最新的store内容

    action.js 
    // 获取用户信息
    export function getUserInfo(params) {
        return async function(dispatch, getState) {
            const { app } = getState()
            const { userInfo } = app
            if (Object.keys(userInfo).length && !params._force) {
                return userInfo
            }
            const user = await authorize.getUserInfoPromise()
            dispatch({
                type: types.UPDATEUSERINFO,
                payload: user
            })
            return {
                userInfo: user
            }
        }
    }
    
    
    index.js
    const mapDispathToProps = (dispath: Dispatch): Actions.AppActionsMethodTypes => {
        return {
            getUserInfo: (parmas) => {
            	// dispatch传入的action是一个函数
                return dispath(Actions.getUserInfo(parmas))
            }
        }
    }
    this.props.getUserInfo({})
    
    
    redux-thunk.js
    if (typeof action === 'function') {
        return action(dispatch, getState, extraArgument);
    }
    

    显然redux-thunk是通过一种劫持的手段,来支持了传入的action是function的场景

    那么我们在来看下为什么要 return next(action); 这个next是什么?redux中间件说的洋葱模型又是什么?

    先看下redux中间件的书写及调用方式,以redux-thunk为例

    redux-tunk.js
    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;
    
    
    store.js
    const middlewares = [thunk]
    const middlewareEnhancer = applyMiddleware(...middlewares)
    
    const store = createStore(createReducer(), preloadState, middlewareEnhancer)
    return store
    

    调用applyMiddleware方法传入middlewares参数

    看下applyMiddleware的源码实现

    export default function applyMiddleware(
      ...middlewares
    ){
      return (createStore) =>
        (
          reducer,
          preloadedState
        ) => {
          const store = createStore(reducer, preloadedState)
          let dispatch: Dispatch = () => {
            throw new Error(
              'Dispatching while constructing your middleware is not allowed. ' +
                'Other middleware would not be applied to this dispatch.'
            )
          }
    
          const middlewareAPI: MiddlewareAPI = {
            getState: store.getState,
            dispatch: (action, ...args) => dispatch(action, ...args)
          }
          const chain = middlewares.map(middleware => middleware(middlewareAPI))
          dispatch = compose(...chain)(store.dispatch)
    
          return {
            ...store,
            dispatch
          }
        }
    }
    

    applyMiddleware接受一个middlewares数组,然后返回一个函数,这个函数接受一个createStore参数,其实这就是一个闭包

    然后我们在看下

    createStore(createReducer(), preloadState, middlewareEnhancer)
    
    redux.js
    function createStore(reducer, preloadedState, enhancer) {
    
      if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
          throw new Error(
            `Expected the enhancer to be a function. Instead, received: '${kindOf(
              enhancer
            )}'`
          )
        }
    
        return enhancer(createStore)(reducer,preloadedState)
      }
    }
    
    这里判断是否传入了enhancer函数,也就是传入了applyMiddleware函数的返回值
    

    回过头来看这里

    我们在项目代码内调用

    const store = createStore(createReducer(), preloadState, middlewareEnhancer)
    return store
    
    <Provider store={store}>
        <Root />
    </Provider>
    
    这个store则是applyMiddleware内最终反回的
    return {
       ...store,
       dispatch
    }
    

    然后我们细看下applyMiddleware方法内的实现

    const store = createStore(reducer, preloadedState)
    let dispatch = () => {
    	throw new Error(
    	  'Dispatching while constructing your middleware is not allowed. ' +
    	    'Other middleware would not be applied to this dispatch.'
    	)
    }
    
    const middlewareAPI: MiddlewareAPI = {
    	getState: store.getState,
    	dispatch: (action, ...args) => dispatch(action, ...args)
    }
    // 关键步骤一,往每个中间件函数传入getState,及dispatch这两个方法
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    
    // 关键步骤二,通过compose组合调用中间件函数,然后传入原始的store.dispatch方法
    dispatch = compose(...chain)(store.dispatch)
    
    return {
    	...store,
    	dispatch
    }
    

    加入我们传入三个中间件fn1, fn2,fn3先看下chain得到的是什么

    middlewares数组应该是这样的 [ // 三层函数的中间件
    	({getState, dispatch}) => next1 => action => next1(action), 
    	({getState, dispatch}) => next2 => action => next2(action),
    	({getState, dispatch}) => next3 => action => next3(action)
    ]
    
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    
    // 传入middlewareAPI之后,得到的chain应用事这样的数组 [ // 2层函数的中间件了,此时已经可以去访问调用dispatch与getState变量了
    	next1 => action => next1(action),
    	next2 => action => next2(action),
    	next3 => action => next3(action)
    ]
    

    我们看下compose的实现

    const compose = (...fns) => {
    	if (!fns.length) {
    		return (arg) => arg
    	}
    
    	if (fns.length === 1) {
    		return fns[0]
    	}
    
    	return fns.reduce((a, b) => {
    		return (...args) => {
    			return a(b(...args))
    		}
    	})
    }
    

    compose(fn1, fn2, fn3) => 最终返回的事一个函数 (...args) => {return a(b(...args))}

    我们用个例子来试下

    function fn1(...args) {
    	console.log('fn1', ...args)
    	return 1
    }
    
    function fn2(...args) {
    	console.log('fn2', ...args)
    	return 2
    }
    
    function fn3(...args) {
    	console.log('fn3', ...args)
    	return 3
    }
    
    我们传入一个函数,
    const result1 = compose(fn1)     
    result1(999)
    
    打印结果:fn1 999
    
    传入两个函数,会遍历一次
    const result2 = compose(fn1, fn2)                  
    result2(999)											
    													
    打印结果:fn2 999; fn1 2; 1
    
    我们传入三个函数,会遍历两次
    const result3 = compose(fn1, fn2, fn3) 
    result3(999)
    
    打印结果:fn3 999; fn2 3; fn1 2; 1
    
    所以compose(fn1, fn2, fn3) === (...args) => fn1(fn2(fn3(...args)))
    
    compose的作用:从右至左依次执行函数,将上一个函数的执行结果传入到下一个函数
    
    
    // 在来看这行代码
    
    dispatch = compose(...chain)(store.dispatch)
    
    拆开来看
    
    第一步
    const composeMiddleware = compose(...chain)
    
    composeMiddleware的值事这样的 (...args) => fn1(fn2(fn3(...args)))
    
    第二步
    
    dispatch = composeMiddleware(store.dispatch)
    
    dispatch的值如下所示fn1(fn2(fn3(store.dispatch))),也就是第一个中间件函数最终返回的值,通过前面的步骤我们可以知道chain数组如下所示
    [
    	next1 => action => next1(action),
    	next2 => action => next2(action),
    	next3 => action => next3(action)
    ]
    
    那么fn1(fn2(fn3(store.dispatch)))调用过程如下所示
    
    fn3(store.dispatch)调用 传入参数 next3(store.dispatch)  返回值为action => store.dispatch(action)
    fn2(fn3调用返回参数)调用  传入参数 next2(action => store.dispatch(action)) 返回值为action => (action => store.dispatch(action))(action)
    fn1(fn2调用返回参数)调用  传入参数 next1(action => (action => store.dispatch(action))(action))  返回值为 (action => (action => store.dispatch(action))(action))(action)
    
    
    调用完之后的返回值是fn1 next => action => next(action) 调用的返回值 action => next(action), 这个next就是fn2调用返回的值
    
    dispatch = action => next1(action) (也就是fn1的返回值)
    

    实际上将每个fn函数都执行一边,第一个执行的函数,也就是最右边的函数,会接受到一个store.dispatch函数,后面的函数接受到的都是上一个函数执行的返回结果

    我们看下洋葱模型,定义三个中间件logger1、logger2、logger3

    function logger1(...args) { // 这一层函数的目的,可以帮助中间件定义的时候传入一些自定义参数
        return function({dispatch, getState}) { // 这一层函数的目的是,接受getState,dispatch函数,可以拿到store内容,并做一些操作
            return function (next) { // 接受store.dispatch参数或者下一个中间件的返回函数
                return function (action) {
                    console.log('logger1 enter', action)
                    const result = next(action)
                    console.log('logger1 outer', result)
                    return result
                }
            }
        }
    }
    
    const log1 = logger1()
    
    function logger2(...args) {
        return function({dispatch, getState}) {
            return function (next) {
                return function (action) {
                    console.log('logger2 enter', action)
                    const result = next(action)
                    console.log('logger2 outer', result)
                    return result
                }
            }
        }
    }
    
    const log2 = logger2()
    
    function logger3(...args) {
        return function({dispatch, getState}) {
            return function (next) {
                return function (action) {
                    console.log('logger3 enter', action)
                    const result = next(action)
                    console.log('logger3 outer', result)
                    return result
                }
            }
        }
    }
    
    const log3 = logger3()
    
    const obj = {
        getState: () => {},
        dispatch: () => {}
    }
    
    const chain = [log1(obj), log2(obj), log3(obj)]
    
    var dispatch = (...args) => {
        console.log('原始 dispatch')
        return args
    }
    
    const newDispatch = compose1(...chain)(dispatch)
    
    console.log('last', newDispatch)
    
    newDispatch({
         type: 'INIT',
         payload: {
             a: 123
         }
    })
    
    
    // 执行结果为
    // logger1 enter {type: "INIT", payload: {…}}
    // logger2 enter {type: "INIT", payload: {…}}
    // logger3 enter {type: "INIT", payload: {…}}
    
    // 原始 dispatch
    
    // logger3 outer {type: "INIT", payload: {…}}
    // logger2 outer {type: "INIT", payload: {…}}
    // logger1 outer {type: "INIT", payload: {…}}
    
    

    这个就是洋葱模型,如下图所示

    image

    到这里我们已经知道,redux的中间件是怎么去设计的了,同时也知道中间件为什么需要定义4层函数,同时我们在看之前的一个细节

    // 这里定义了一次dispatch函数
    let dispatch: Dispatch = () => {
    	throw new Error(
    	  'Dispatching while constructing your middleware is not allowed. ' +
    	    'Other middleware would not be applied to this dispatch.'
    	)
    }
    
    const middlewareAPI: MiddlewareAPI = {
    	getState: store.getState,
    	dispatch: (action, ...args) => dispatch(action, ...args) // 这里传入的dispatch方法,内部调用的就是上面定义的dispatch方法,这里只所以没有直接传入store.dispatch,就是为了避免中间件在初始化的时候,或者执行的时候去修改了原始的dispatch方法
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)
    

    总体来说,中间件的实现的关键思路是使用compose组合的方式,将一系列函数组合成如下函数(...args) => fn1(fn2(fn3(...args))),即将最后一个函数调用的返回值当成参数传入到前一个函数,当第一个函数被调用的时候,依次调用传入的参数,直到把传入的参数传入最后一个函数;

    middleware