Skip to content

Redux - 中间件

默认情况下,Redux 的分发(dispatch)机制是同步的(synchronous)。当你分发一个 action 时,state 会立即被 reducer 更新。但对于从 API 获取数据这样的异步操作(asynchronous operations)怎么办?

这就是**中间件(middleware)**的作用。中间件提供了一个第三方扩展点,位于 action 被分发之后、到达 reducer 之前。它允许你拦截 action,修改它们,延迟它们,分发其他 action,或者与外部服务交互。

可以将中间件视为一个函数链,它位于 store.dispatch(action) 和你的根 reducer 之间。每个中间件都可以检查 action,执行任务,然后将 action(或一个不同的 action,或什么都不做)传递给链中的下一个中间件,最终到达 reducer。

中间件的常见用例包括:

  • 处理异步操作(例如,API 调用)。
  • 记录 actions 和 state 变化。
  • 报告错误。
  • 根据分发的 actions 进行 API 调用。
  • 路由(Routing)。

Redux 提供了 applyMiddleware 函数来增强 store 的中间件功能。然而,推荐的 Redux store 设置方式是使用 Redux Toolkit 的 configureStore 函数,它简化了中间件的配置。

// 使用 Redux Toolkit(推荐)
import { configureStore } from '@reduxjs/toolkit';
import logger from 'redux-logger'; // 示例中间件
import rootReducer from './reducers'; // 你的组合 reducer
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger), // 将 logger 添加到默认中间件中
});
// 旧方式(用于理解)
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'; // 示例中间件
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk, logger));

configureStore 默认会自动包含一些有用的中间件,其中最重要的是 redux-thunk。你可以使用 middleware 选项来自定义中间件栈,通常会使用 getDefaultMiddleware 来包含默认的中间件以及你自定义的中间件。

一个 Redux 中间件是一个高阶函数(higher-order function),其签名(signature)为:store => next => action => { ... }。

  • store:提供了访问 store 的 getState 和 dispatch 方法的权限。
  • next:一个将 action 传递给链中下一个中间件的函数,如果它是链中的最后一个,则传递给 reducer。你必须调用 next(action) 才能让 action 最终到达 reducer(除非你打算阻止它)。
  • action:被分发(dispatched)的 action 对象。

让我们编写一个简单的自定义 logger 中间件:

const simpleLogger = store => next => action => {
console.log('Dispatching action:', action);
const result = next(action); // 将 action 传递给下一个中间件/reducer
console.log('Next state:', store.getState());
return result; // 返回 next(action) 的结果
};
// 与 configureStore 一起使用:
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(simpleLogger),
});

redux-thunk 是处理基本异步逻辑的标准中间件。它允许 action creator 返回一个函数(称为 “thunk”),而不是一个普通的 action 对象。这个函数接收 dispatch 和 getState 作为参数,允许它异步地分发 actions(例如,在 API 调用之后)。

Thunk Action Creator 示例:

// actions/userActions.js
import { userApi } from './api'; // 假设这包含 API 调用函数
// Action 类型(通常由 createSlice 生成)
const USER_FETCH_REQUEST = 'user/fetchRequest';
const USER_FETCH_SUCCESS = 'user/fetchSuccess';
const USER_FETCH_FAILURE = 'user/fetchFailure';
// Thunk Action Creator
export const fetchUser = (userId) => async (dispatch, getState) => {
dispatch({ type: USER_FETCH_REQUEST });
try {
const user = await userApi.fetchById(userId);
dispatch({ type: USER_FETCH_SUCCESS, payload: user });
} catch (error) {
dispatch({ type: USER_FETCH_FAILURE, payload: error.message });
}
};

你会像分发常规 action 一样分发这个 thunk:store.dispatch(fetchUser(123));

使用 Redux Toolkit 的现代异步处理:createAsyncThunk

Section titled “使用 Redux Toolkit 的现代异步处理:createAsyncThunk”

Redux Toolkit 提供了 createAsyncThunk 来简化标准的异步请求生命周期(pending/fulfilled/rejected)。

// features/users/usersSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { userApi } from './api';
// 由 createAsyncThunk 生成的异步 thunk
export const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus', // Action 类型前缀
async (userId, thunkAPI) => {
// 第一个参数是 payload creator 回调函数
const response = await userApi.fetchById(userId);
return response.data; // 这将成为 'fulfilled' action 的 payload
// 如果抛出错误,这将成为 'rejected' action 的 payload
}
);
const usersSlice = createSlice({
name: 'users',
initialState: { entity: null, loading: 'idle', error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserById.pending, (state) => {
state.loading = 'pending';
state.entity = null;
state.error = null;
})
.addCase(fetchUserById.fulfilled, (state, action) => {
state.loading = 'succeeded';
state.entity = action.payload; // 来自异步函数的 Payload
})
.addCase(fetchUserById.rejected, (state, action) => {
state.loading = 'failed';
state.error = action.error.message; // 来自异步函数的 Error
});
},
});
export default usersSlice.reducer;
// 分发:
// store.dispatch(fetchUserById(123));

createAsyncThunk 会自动分发 pending/fulfilled/rejected actions,你可以在 slice 的 extraReducers 中处理它们。

对于更复杂的异步流程或管理副作用,还有其他中间件可用,如 redux-saga(使用生成器函数)或 redux-observable(使用 RxJS Observables),但 redux-thunk(特别是通过 createAsyncThunk)对于大多数常见用例已经足够了。