说说对 Redux 中间件的理解?常用的中间件有哪些?

一、是什么

Redux 中间件是在 dispatch 一个 action 到 action 到达 reducer 之间的扩展点。它提供了一种机制来拦截、修改、延迟、替换或副作用化 action 的处理过程。

dispatch(action) → Middleware1 → Middleware2 → ... → Reducer → newState

没有中间件时,dispatch(action) 会同步地将 action 传给 reducer。中间件打破了这个同步约束,让我们可以在这个链路中做任何事情:打日志、发异步请求、条件拦截等。

二、中间件的函数签名

Redux 中间件采用三层柯里化(curried function)结构:

const myMiddleware = (storeAPI) => (next) => (action) => {
  // storeAPI: { getState, dispatch }
  // next: 调用链中的下一个中间件(或 reducer)
  // action: 被 dispatch 的 action

  // 在 action 到达 reducer 之前做事
  console.log('dispatching', action);

  // 调用下一个中间件
  const result = next(action);

  // action 已经到达 reducer 之后做事
  console.log('next state', storeAPI.getState());

  return result;
};

三个参数的含义:

  • storeAPI:包含 getState()dispatch(),可以读取当前状态或 dispatch 新的 action
  • next:调用链中下一个中间件的 dispatch 函数;如果是最后一个中间件,则指向原始的 store.dispatch
  • action:当前被 dispatch 的 action 对象

三、中间件的执行原理

多个中间件通过 applyMiddleware 或 RTK 的 configureStore 组合形成一条链。执行过程类似洋葱模型:

dispatch(action)


┌─── Middleware A(前半段)───┐
│   ┌─── Middleware B(前半段)───┐
│   │   ┌─── Middleware C(前半段)───┐
│   │   │                            │
│   │   │     Reducer 执行           │
│   │   │                            │
│   │   └─── Middleware C(后半段)───┘
│   └─── Middleware B(后半段)───┘
└─── Middleware A(后半段)───┘

核心实现原理(简化版):

function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState) => {
    const store = createStore(reducer, preloadedState);
    let dispatch = store.dispatch;

    const storeAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action),
    };

    const chain = middlewares.map(middleware => middleware(storeAPI));
    dispatch = compose(...chain)(store.dispatch);

    return { ...store, dispatch };
  };
}

function compose(...fns) {
  return fns.reduce((a, b) => (...args) => a(b(...args)));
}

compose[A, B, C] 组合为 A(B(C(dispatch))),这样 A 的 next 指向 B 的处理函数,B 的 next 指向 C 的处理函数,C 的 next 指向原始 dispatch。

四、常用中间件

1. redux-thunk — 异步处理

redux-thunk 是最简单的异步中间件,允许 dispatch 一个函数(而非普通对象)。RTK 默认内置了它。

// thunk action creator
function fetchUser(userId) {
  return async (dispatch, getState) => {
    dispatch({ type: 'user/loading' });
    try {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      dispatch({ type: 'user/loaded', payload: data });
    } catch (error) {
      dispatch({ type: 'user/error', payload: error.message });
    }
  };
}

// 使用
store.dispatch(fetchUser(123));

thunk 中间件的实现极其简洁:

function thunkMiddleware({ dispatch, getState }) {
  return (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState);
    }
    return next(action);
  };
}

只有 11 行代码:如果 action 是函数,执行它并传入 dispatch 和 getState;否则正常传递给下一个中间件。

2. redux-saga — 复杂异步流

redux-saga 使用 ES6 Generator 函数管理副作用,适用于复杂的异步流程(竞态、取消、重试、并行等):

import { call, put, takeLatest, all, delay } from 'redux-saga/effects';

function* fetchUserSaga(action) {
  try {
    yield put({ type: 'user/loading' });
    const user = yield call(api.fetchUser, action.payload);
    yield put({ type: 'user/loaded', payload: user });
  } catch (error) {
    yield put({ type: 'user/error', payload: error.message });
  }
}

function* searchSaga(action) {
  yield delay(300); // 防抖
  const results = yield call(api.search, action.payload);
  yield put({ type: 'search/results', payload: results });
}

function* rootSaga() {
  yield all([
    takeLatest('user/fetch', fetchUserSaga),   // 自动取消之前的请求
    takeLatest('search/query', searchSaga),
  ]);
}

saga 的优势:

  • takeLatest 自动处理竞态条件(取消上一个未完成的请求)
  • Generator 让异步流程读起来像同步代码
  • 可以用 call 的 mock 轻松测试
  • 支持 forkcancelrace 等复杂并发模式

3. redux-logger — 日志记录

在开发环境记录每一次 action 和状态变化:

import logger from 'redux-logger';

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(logger),
});

输出示例:

action counter/increment @ 14:23:45.123
  prev state { counter: { value: 0 } }
  action     { type: 'counter/increment' }
  next state { counter: { value: 1 } }

4. RTK 内置中间件

Redux Toolkit 的 configureStore 默认配置了以下中间件:

const store = configureStore({
  reducer: rootReducer,
  // 默认中间件包括:
  // - redux-thunk(异步支持)
  // - serializableCheck(检测不可序列化的值)
  // - immutableCheck(检测意外的 state 变更,开发环境)
});

五、手写一个简单的中间件

日志中间件

const loggerMiddleware = (storeAPI) => (next) => (action) => {
  console.group(action.type);
  console.log('dispatching:', action);
  console.log('prev state:', storeAPI.getState());

  const result = next(action);

  console.log('next state:', storeAPI.getState());
  console.groupEnd();

  return result;
};

错误上报中间件

const crashReporter = (storeAPI) => (next) => (action) => {
  try {
    return next(action);
  } catch (err) {
    console.error('Caught an exception!', err);
    reportToServer({
      error: err,
      action,
      state: storeAPI.getState(),
    });
    throw err;
  }
};

条件 dispatch 中间件

const conditionalMiddleware = (storeAPI) => (next) => (action) => {
  if (action.meta?.skipIf) {
    const state = storeAPI.getState();
    if (action.meta.skipIf(state)) {
      return; // 跳过此 action
    }
  }
  return next(action);
};

// 使用
dispatch({
  type: 'counter/increment',
  meta: {
    skipIf: (state) => state.counter.value >= 10,
  },
});

节流中间件

function createThrottleMiddleware(interval = 300) {
  const pending = {};

  return (storeAPI) => (next) => (action) => {
    const key = action.type;

    if (action.meta?.throttle) {
      if (pending[key]) return;

      pending[key] = true;
      setTimeout(() => { pending[key] = false; }, interval);
    }

    return next(action);
  };
}

六、RTK 中使用自定义中间件

import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware()
      .prepend(conditionalMiddleware) // 在默认中间件之前
      .concat(loggerMiddleware),      // 在默认中间件之后
});

注意 getDefaultMiddleware() 返回的是 RTK 默认中间件数组(包含 thunk 等),可以链式调用 prependconcat 添加自定义中间件。

七、中间件选型建议

中间件适用场景复杂度学习成本
redux-thunk简单异步(API 调用)
redux-saga复杂异步流(竞态、取消、轮询)
RTK createAsyncThunk标准 CRUD 操作
RTK Query服务端数据缓存和同步
自定义中间件日志、埋点、错误上报等横切关注点

对于大多数项目,RTK 内置的 thunk + createAsyncThunk 已经足够。只有在需要精细控制异步流程(如 WebSocket 管理、复杂的并发调度)时,才考虑引入 redux-saga。

八、总结

Redux 中间件是一个强大的扩展机制,它以优雅的函数式设计(三层柯里化 + compose 组合)实现了 AOP(面向切面编程)的能力。理解中间件的工作原理,不仅有助于使用现有中间件,更能帮助你在需要时编写自定义中间件来解决特定业务问题。