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. 最佳实践

  1. 按功能拆分 Saga 文件
  2. 使用常量定义 Action 类型
  3. 为 Saga 编写单元测试
  4. 合理使用 takeEvery 和 takeLatest
  5. 错误处理要完整
  6. 避免在 Saga 中使用副作用函数,用 Effects 替代

Redux-Saga 让异步流程更可控,特别适合复杂异步场景,但也需要权衡其学习曲线和项目规模。

posted @ 2026-03-11 09:28  箫笛  阅读(1)  评论(0)    收藏  举报