对redux middleware的理解

238 阅读4分钟

什么是middleware

action - reducer中加入逻辑,提供逻辑插入点;也可以称之为store的enhencer;store enhencer是高阶函数(接受function作为参数,并且return function);
注意:部分代码为伪代码

如何开发一个middleware

首先,我们要知道,我们是如何加入一个middleware的;初始化代码: middleware的格式:

const middleware = store => next => action => {
    // your code
    next(action);
}

核心-compose

目的:为了方便地书写深度嵌套的函数,而不用手动一次一次地调用,具体实现就是使用了reduce compose源码中可以看到,就是使用reduce,将函数串联起来,即,入参a,b,c,返回:a(b(c(...args)));

export default function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return <T>(arg: T) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce(
    (a, b) =>
      (...args: any) =>
        a(b(...args))
  )
}

applymiddleware源码:

export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return (createStore: StoreEnhancerStoreCreator) =>
    <S, A extends AnyAction>(
      reducer: Reducer<S, A>,
      preloadedState?: PreloadedState<S>
    ) => {
      // 创建store
      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)
      }
      // 函数柯里化,嵌套层次太深,预先传入参数
      /// 所有middleware传入middlewareapi-第一次,预先传入getstate和dispatch;
      // 此时的chain,只接受next和action参数
      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      // 在compose中,进行函数的组合,之后在这里,compose之后,得到middlware1(middlware2(...args))
      // 预先传入next参数,next就是store.dispatch, middlware1(middlware2(...args))(store.dispatch),为最后一个middleware传入dispatch参数
      dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
      // dispatch被覆盖为包装过的dispatch,新的dispatch其实是middlware1(middlware2(...args))(store.dispatch)
      // 接受action,最后调用dispatch的时候,就是在执行dispatch(action),
      // middlware1(middlware2(...args))(store.dispatch)(action)
      // middleware3接受的next是dispatch,其余的next是上一个函数,1的next是2,2的next是3,3的next是dispatch
      // 因此,在上一个middlware中,需要手动执行next(action),才能顺利执行下一个middleware;
      return {
        ...store,
        dispatch
      }
    }
}

上面注释如果不清楚的,可以看这里:

const dispatch = (action) => {
  console.log('dispatch', action);
}
function a(next) {
  return (action) => {
    console.log(action, 'a', next);
    next(action);
  };
}
function b(next) {
  return (action) => {
    console.log(action, 'b', next);
    next(action)
  };
}
function c(next) {
  return (action) => {
    console.log(action, 'c', next);
    next(action);
  };
}
// 执行应该是c-b-a;但是c返回的函数是b的入参,b返回的是a的入参数,最后返回的是a函数,('test')action从a函数开始;
const fn = a(b(c(dispatch)));
console.log(fn);
// (action) => {
//   console.log(action, 'a', next);
//   next(action);
// }
fn('test');
// 执行结果
// test a (action) => {
//   console.log(action, 'b', next);
//   next(action)
// }
// test b (action) => {
//   console.log(action, 'c', next);
//   next(action);
// }
// test c (action) => {
// console.log('dispatch', action);
// }
// dispatch test

简单的示例,一样的原理,a的next是b,b的next是c,c的next是最初调用传入的值dispatch

如何理解redux-thunk

what

什么是thunk呢? 简单理解,就是函数返回一个函数,return的function就是thunk,它推迟了内部返回函数逻辑的执行,如下所示就是thunk thunk可以是匿名函数,也可以是具名函数

function wrapFn() {
  // 这里就是一个thunk,被推迟了执行
  return () => {
    console.log('i am a thunk function')
  }
}

如上可见,我们如果要调用这个thunk,我们需要调用两次wrapper function,即wrapFn()();但是redux在上面的分析中,做了的处理,可以让我们只调用一次就正常调用;

why-为什么异步我们需要thunk?

redux重要的概念有action,reducer,store,actionCreator,dispatch; 如果需要触发一个state的改动,那需要dispatch一个action,action会被传递到reducer当中,reducer是纯函数,action是一个普通的plain object; redux-thunk允许我们在actioncreator中使用function
同步操作:直接触发普通的action即可

function add() {
  return {
    type: 'ADD',
    payload: 1,
  }
}

异步操作:普通的异步调用函数如下

function serverAdd() {
    return fetch.get('/xxxxx').then((res) => {
      // your code
    });
}

reducer是纯函数,不能在reducer中做异步,应该在action做异步,普通的纯对象,无法满足异步(通常使用promise)的要求,我们可以返回一个函数,在函数中做异步操作 此时会发现,actioncreator返回的是function,无法正确识别,触发reducer;
如果不使用thunk,我们会如何操作?如何实现?
不使用thunk,我们可以手动将dispatch传入到函数,在异步完成之后,再调用dispatch

function serverAdd(dispatch, userId) {
    return fetch.get(`/${userId}`).then((res) => {
      // your code
      dispatch({type: 'ADD'});
    });
}

此时,每个方法都需要手动传入dispatch,而且需要明确地区分方法是同步/异步方法,会使代码变得复杂,不易于扩展; thunk让我们哪些变得简单了 使用thunk,我们无需手动去传递dispatch,调用thunk就像调用普通的action一样自然;而且也可以识别到function

function serverAdd(userId) {
    return dispatch => fetch.get(`/${userId}`).then((res) => {
      // your code
      dispatch({type: 'ADD'});
    });
}
// 调用
dispatch(serverAdd(1));

how-原理

thunk的源码解析 上面可以看到,thunk是返回了一个函数,只要加入一个中间件,能够识别到函数类的action,并对这类action直接调用即可;

function createThunkMiddleware<
  State = any,
  BasicAction extends Action = AnyAction,
  ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
  // Standard Redux middleware definition pattern:
  // See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
  const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
    ({ dispatch, getState }) =>
    next =>
    action => {
      // The thunk middleware looks for any functions that were passed to `store.dispatch`.
      // If this "action" is really a function, call it and return the result.
      if (typeof action === 'function') {
        // Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
        return action(dispatch, getState, extraArgument)
      }

      // Otherwise, pass the action down the middleware chain as usual
      return next(action)
    }
  return middleware
}

thunk中间件,每一个action都会经过thunk,thunk进行判断,如果是函数类型的,直接执行,否则,next其实就是dispatch,dispatch普通的action

竟品

redux-saga-不同点

参考:
daveceddia.com/what-is-a-t…
github.com/reduxjs/red… stackoverflow.com/questions/3… medium.com/@istvanistv…