Skip to content

redux-saga(管理复杂的异步流程)

redux-saga 是一个用于管理 Redux 应用程序中复杂异步逻辑的中间件。它使用 ES6 的生成器函数(generators)来处理副作用,如 API 请求、订阅等,并提供了一套丰富的工具和抽象层,使得编写、测试和调试异步代码变得更加容易。

核心概念

  1. Saga:类似于守护进程或后台任务,负责监听特定的动作并执行相应的副作用操作。
  2. Effect:由 redux-saga 提供的一组辅助函数,用于描述要执行的副作用,比如调用函数、分发动作等。它们不是立即执行的,而是返回一个描述对象,由 redux-saga 内部机制处理。
  3. Middlewareredux-saga 本身是一个 Redux 中间件,它拦截 action 并根据需要启动 sagas 来处理这些 action。
  4. Yield:生成器函数中的关键字,用于暂停执行并将控制权交给 redux-saga,等待 Effect 完成后再继续执行。

安装

首先,你需要安装 redux-saga

bash
npm install redux-saga

配置 Store

接下来,你需要在创建 Redux store 时应用 redux-saga 中间件。你可以使用 createSagaMiddleware 函数来创建中间件实例,并将其传递给 applyMiddleware

javascript
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import rootReducer from "./reducers"; // 导入你的根 reducer
import rootSaga from "./sagas"; // 导入你的根 saga

// 创建 saga middleware
const sagaMiddleware = createSagaMiddleware();

// 创建 store 并应用 saga middleware
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));

// 运行根 saga
sagaMiddleware.run(rootSaga);

编写 Sagas

创建简单的 Saga

Sagas 是通过定义生成器函数来编写的。这些函数可以 yield 特殊的对象(称为 Effects),以指示 redux-saga 执行某些操作。

javascript
import { takeEvery, put, call } from "redux-saga/effects";

function* fetchUser(action) {
  try {
    const user = yield call(
      fetch,
      `https://api.example.com/users/${action.payload.id}`
    );
    const response = yield user.json();
    yield put({ type: "FETCH_USER_SUCCESS", payload: response });
  } catch (error) {
    yield put({ type: "FETCH_USER_FAILURE", payload: error });
  }
}

export function* watchFetchUser() {
  yield takeEvery("FETCH_USER_REQUEST", fetchUser); // 监听 FETCH_USER_REQUEST 动作并运行 fetchUser
}

在这个例子中,fetchUser 是一个 saga,它会在接收到 FETCH_USER_REQUEST 动作时被触发。它会发起一个 HTTP 请求,然后根据请求的结果 dispatch 成功或失败的动作。

组合多个 Sagas

通常你会有一个根 saga 来组合所有的子 sagas。你可以使用 all 效应来并发地运行多个 sagas。

javascript
import { all } from "redux-saga/effects";
import { watchFetchUser } from "./userSagas";
import { watchFetchPosts } from "./postSagas";

export default function* rootSaga() {
  yield all([
    watchFetchUser(),
    watchFetchPosts(),
    // ...其他 sagas
  ]);
}

使用 Sagas

一旦你定义了 sagas 并将它们与 Redux store 结合起来,你就可以像往常一样 dispatch actions 了。redux-saga 会自动监听这些 actions 并启动相应的 sagas。

jsx
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import { fetchUserRequest } from "./actions"; // 假设你已经定义了这个 action creator

function UserProfile({ userId }) {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchUserRequest(userId)); // 分发动作,触发 saga
  }, [dispatch, userId]);

  return <div>User profile component</div>;
}

export default UserProfile;

处理并发和批处理

redux-saga 提供了许多高级特性来帮助你处理复杂的场景,例如并发请求、取消正在进行的任务、批处理 actions 等。这里是一些常见的模式:

  • 并发:使用 allfork 来并发运行多个 sagas。
javascript
import { all, fork } from "redux-saga/effects";

function* fetchDataConcurrently() {
  yield all([fork(fetchUser, { id: 1 }), fork(fetchPost, { id: 101 })]);
}
  • 批处理:使用 takeLatestthrottle 来限制频繁发生的 actions。
javascript
import { takeLatest } from "redux-saga/effects";

function* watchFetchUserThrottled() {
  yield takeLatest("FETCH_USER_REQUEST", fetchUser); // 只处理最新的 FETCH_USER_REQUEST 动作
}

测试 Sagas

由于 sagas 是纯 JavaScript 函数,因此它们非常容易测试。你可以使用 redux-saga/test-plan 或者任何你喜欢的测试框架来进行单元测试。

javascript
import { runSaga } from "redux-saga";
import { put, call } from "redux-saga/effects";
import { fetchUser } from "./sagas";
import * as api from "./api";

test("fetchUser saga calls the correct API and dispatches success action", async () => {
  const mockUserId = 1;
  const mockUser = { id: 1, name: "John Doe" };
  const dispatched = [];

  const apiResponse = Promise.resolve({
    json: () => Promise.resolve(mockUser),
  });

  jest.spyOn(api, "fetch").mockImplementationOnce(() => apiResponse);

  await runSaga({ dispatch: (action) => dispatched.push(action) }, fetchUser, {
    type: "FETCH_USER_REQUEST",
    payload: { id: mockUserId },
  }).done;

  expect(api.fetch).toHaveBeenCalledWith(
    `https://api.example.com/users/${mockUserId}`
  );
  expect(dispatched).toEqual([
    { type: "FETCH_USER_SUCCESS", payload: mockUser },
  ]);
});

总结

redux-saga 提供了一种强大且灵活的方式来管理和组织 Redux 应用中的异步逻辑和其他 side effects。通过使用生成器函数和丰富的 Effect API,它可以简化复杂的业务流程,同时保持代码的可读性和可维护性。

Released under the MIT License.