Redux - redux-saga 中间件使用指南
Redux-Saga 是一个用于管理 Redux 应用副作用的中间件,通过 ES6 的 Generator 函数让异步流程更易管理。
1. 基础安装与配置
npm install redux-saga
// store.js
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from './reducers'
import rootSaga from './sagas'
// 创建 saga 中间件
const sagaMiddleware = createSagaMiddleware()
// 应用中间件
const store = createStore(
rootReducer,
applyMiddleware(sagaMiddleware)
)
// 运行 root saga
sagaMiddleware.run(rootSaga)
export default store
2. 核心概念与 Effects
常用 Effects
import {
take, // 等待 action
takeEvery, // 监听每个 action
takeLatest, // 监听最后一个 action
put, // 触发 action
call, // 调用函数(用于异步)
fork, // 非阻塞调用
cancel, // 取消任务
select, // 获取 state
all, // 并行执行
race // 竞速执行
} from 'redux-saga/effects'
3. 基本使用示例
用户登录示例
// sagas/userSaga.js
import { call, put, takeEvery } from 'redux-saga/effects'
import { loginApi } from '../api/user'
import { loginSuccess, loginFailure } from '../actions/user'
// worker Saga
function* login(action) {
try {
const { username, password } = action.payload
const response = yield call(loginApi, username, password)
yield put(loginSuccess(response.data))
} catch (error) {
yield put(loginFailure(error.message))
}
}
// watcher Saga
function* watchLogin() {
yield takeEvery('LOGIN_REQUEST', login)
}
export default watchLogin
API 调用示例
// api.js
export const fetchData = (id) => {
return fetch(`/api/data/${id}`).then(res => res.json())
}
// saga.js
import { call, put } from 'redux-saga/effects'
import { fetchData } from './api'
function* fetchDataSaga(action) {
try {
const data = yield call(fetchData, action.id)
yield put({ type: 'FETCH_SUCCESS', payload: data })
} catch (error) {
yield put({ type: 'FETCH_ERROR', error })
}
}
4. 高级用法
1. 组合多个 Saga
// sagas/index.js
import { all } from 'redux-saga/effects'
import userSaga from './userSaga'
import productSaga from './productSaga'
export default function* rootSaga() {
yield all([
userSaga(),
productSaga()
])
}
2. 竞态处理 (race)
import { race, call, put, delay } from 'redux-saga/effects'
function* fetchWithTimeout() {
try {
const { data, timeout } = yield race({
data: call(fetchApi),
timeout: delay(5000) // 5秒超时
})
if (data) {
yield put({ type: 'SUCCESS', data })
} else {
yield put({ type: 'TIMEOUT' })
}
} catch (error) {
yield put({ type: 'ERROR', error })
}
}
3. 取消任务
import { take, fork, cancel, cancelled } from 'redux-saga/effects'
function* backgroundTask() {
try {
while (true) {
// 执行某些操作
}
} finally {
if (yield cancelled()) {
// 清理工作
}
}
}
function* watchStartBackgroundTask() {
while (yield take('START_TASK')) {
const task = yield fork(backgroundTask)
yield take('STOP_TASK')
yield cancel(task)
}
}
4. 获取 State
import { select } from 'redux-saga/effects'
function* checkUserRole() {
// 选择整个 state
const state = yield select()
// 选择部分 state
const user = yield select(state => state.user)
const isAdmin = yield select(state => state.user.role === 'admin')
// 使用选择器
const userRole = yield select(getUserRole)
}
5. 错误处理
import { delay } from 'redux-saga/effects'
function* fetchWithRetry() {
let retries = 3
while (retries > 0) {
try {
const data = yield call(api.fetch)
yield put({ type: 'SUCCESS', data })
break
} catch (error) {
retries--
if (retries === 0) {
yield put({ type: 'FAILURE', error })
} else {
yield delay(1000) // 等待1秒后重试
}
}
}
}
6. 测试 Saga
import { expectSaga } from 'redux-saga-test-plan'
import { call } from 'redux-saga/effects'
import { fetchUserSaga } from './sagas'
import api from './api'
describe('fetchUserSaga', () => {
it('fetches user successfully', () => {
const user = { id: 1, name: 'John' }
return expectSaga(fetchUserSaga, { payload: 1 })
.provide([
[call(api.fetchUser, 1), user]
])
.put({ type: 'FETCH_SUCCESS', payload: user })
.run()
})
})
7. 最佳实践
- 按功能拆分 Saga 文件
- 使用常量定义 Action 类型
- 为 Saga 编写单元测试
- 合理使用 takeEvery 和 takeLatest
- 错误处理要完整
- 避免在 Saga 中使用副作用函数,用 Effects 替代
Redux-Saga 让异步流程更可控,特别适合复杂异步场景,但也需要权衡其学习曲线和项目规模。

浙公网安备 33010602011771号