第4章:Redux中间件三剑客——thunk、saga与observable
说到Redux中间件,我得先坦白一件事。我刚用Redux那会儿,觉得中间件这玩意儿可有可无。直到项目里塞满了各种异步请求、竞态条件、取消操作……嗯,那场面,现在想起来都头疼。
中间件说白了,就是Redux的「插件系统」。它让你能在dispatch action和reducer处理之间,插入自定义逻辑。今天咱们就聊聊最主流的三个:redux-thunk、redux-saga、redux-observable。
4.1 redux-thunk:最朴素的异步方案
thunk是什么?就是一个返回函数的函数。在Redux里,thunk允许你dispatch一个函数,而不是普通的action对象。
核心思想:让action creator返回函数,而不是对象。
// 普通action creator
const fetchUser = (id) => ({
type: 'FETCH_USER',
payload: id
});
// thunk action creator
const fetchUserAsync = (id) => {
return async (dispatch, getState) => {
dispatch({ type: 'FETCH_USER_START' });
try {
const user = await api.getUser(id);
dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
} catch (error) {
dispatch({ type: 'FETCH_USER_ERROR', payload: error });
}
};
};
我在项目中遇到过一个问题:多个组件同时请求同一个用户数据。用thunk的话,你得自己写缓存逻辑。说白了,thunk就是个「轻量级选手」,适合中小型项目。
我的建议:如果项目异步逻辑简单,没有复杂的竞态控制,thunk完全够用。别为了用saga而用saga。
4.2 redux-saga:用Generator掌控副作用
saga的底层是ES6的Generator函数。它让你能用「同步写法」处理异步流程。你想想看,这多爽?
import { takeEvery, call, put } from 'redux-saga/effects';
function* fetchUserSaga(action) {
try {
yield put({ type: 'FETCH_USER_START' });
const user = yield call(api.getUser, action.payload);
yield put({ type: 'FETCH_USER_SUCCESS', payload: user });
} catch (error) {
yield put({ type: 'FETCH_USER_ERROR', payload: error });
}
}
function* watchFetchUser() {
yield takeEvery('FETCH_USER_REQUEST', fetchUserSaga);
}
saga最牛的地方是什么?是它的「可测试性」和「流程控制」。
| 特性 | thunk | saga |
|---|---|---|
| 测试难度 | 需要mock API | 纯函数测试,无需mock |
| 竞态处理 | 手动实现 | takeLatest内置 |
| 取消操作 | 不支持 | cancel原生支持 |
| 学习曲线 | 低 | 中高 |
避坑指南:我曾经在一个项目里滥用saga,把简单的请求也写成saga。结果代码量翻了三倍,新人根本看不懂。记住:saga是为复杂异步场景准备的,不是万能药。
4.3 redux-observable:响应式编程的优雅
observable基于RxJS,用「流」的思想处理异步。如果你熟悉RxJS,那用起来会非常顺手。
import { ofType } from 'redux-observable';
import { map, switchMap, catchError } from 'rxjs/operators';
import { from, of } from 'rxjs';
const fetchUserEpic = (action$) =>
action$.pipe(
ofType('FETCH_USER_REQUEST'),
switchMap((action) =>
from(api.getUser(action.payload)).pipe(
map((user) => ({ type: 'FETCH_USER_SUCCESS', payload: user })),
catchError((error) => of({ type: 'FETCH_USER_ERROR', payload: error }))
)
)
);
observable最大的优势是什么?是「组合能力」。你可以轻松地把多个流合并、过滤、延迟、重试……
核心对比:
- thunk:函数式,简单直接
- saga:命令式,流程清晰
- observable:声明式,流式处理
4.4 实战选择指南
说了这么多,到底该选哪个?我个人的经验是这样的:
- 团队熟悉度:如果团队没人懂RxJS,别硬上observable
- 异步复杂度:简单请求用thunk,复杂流程用saga
- 测试要求:saga的测试体验最好,thunk最差
- 性能敏感:observable的流式处理性能最优
一个小技巧:我习惯在项目初期用thunk,等异步逻辑变得复杂时,再逐步迁移到saga。别一开始就上重型武器。
4.5 总结
中间件的选择没有银弹。thunk轻量、saga强大、observable优雅。关键看你的项目场景和团队能力。
我记得有个项目,一开始用thunk,后来异步逻辑越来越复杂,竞态条件层出不穷。最后花了三天时间迁移到saga,代码量虽然增加了,但可维护性提升了一个档次。
嗯,这就是中间件的魅力——它让你能优雅地处理副作用,而不是在组件里到处塞异步代码。