Как настроить Redux Saga в проекте?react-40

Общее представление

Redux Saga — это middleware для Redux, который позволяет управлять сайд-эффектами (side effects, побочными эффектами) с помощью генераторов (generators). Чаще всего используется для асинхронных операций, таких как HTTP-запросы.


Установка

Сначала необходимо установить необходимые пакеты:

npm install redux-saga
# или
yarn add redux-saga

Базовая настройка

1. Создание саги

Пример простой саги для обработки API-запроса:

import { call, put, takeEvery } from 'redux-saga/effects';
import { fetchUserSuccess, fetchUserFailure } from './actions';
import api from './api';

// Worker Saga (рабочая сага)
function* fetchUser(action) {
  try {
    const user = yield call(api.fetchUser, action.payload.userId);
    yield put(fetchUserSuccess(user));
  } catch (error) {
    yield put(fetchUserFailure(error.message));
  }
}

// Watcher Saga (наблюдающая сага)
function* userSaga() {
  yield takeEvery('FETCH_USER_REQUEST', fetchUser);
}

export default userSaga;

2. Подключение к Redux Store

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers';
import rootSaga from './sagas';

// Создаем middleware для Saga
const sagaMiddleware = createSagaMiddleware();

// Подключаем к хранилищу
const store = createStore(
  rootReducer,
  applyMiddleware(sagaMiddleware)
);

// Запускаем корневую сагу
sagaMiddleware.run(rootSaga);

export default store;

Основные концепции

Эффекты

Эффекты — это специальные инструкции для middleware:

  • call: Вызывает функцию (часто асинхронную).
  • put: Диспатчит действие (аналог dispatch).
  • takeEvery: Обрабатывает каждое действие указанного типа.
  • takeLatest: Обрабатывает последнее действие, отменяя предыдущие.
  • fork: Запускает сагу неблокирующим способом.
  • all: Запускает несколько саг параллельно.

Пример с несколькими эффектами:

import { all, call } from 'redux-saga/effects';

function* rootSaga() {
  yield all([
    call(userSaga),
    call(productSaga),
  ]);
}

Продвинутые техники

1. Обработка ошибок

function* fetchResource(resource) {
  try {
    const data = yield call(api.fetch, resource);
    yield put({ type: 'FETCH_SUCCESS', data });
  } catch (error) {
    yield put({ type: 'FETCH_ERROR', error });
  }
}

2. Отмена задач

import { race, take, cancel } from 'redux-saga/effects';

function* pollSaga() {
  while (true) {
    yield call(fetchData);
    const { stop } = yield race({
      delay: call(delay, 5000),
      stop: take('STOP_POLLING'),
    });
    if (stop) {
      yield cancel(); // Отмена текущей задачи
    }
  }
}

3. Тестирование саг

import test from 'tape';
import { call, put } from 'redux-saga/effects';
import { fetchUser } from './sagas';
import api from './api';

test('fetchUser Saga test', (assert) => {
  const generator = fetchUser({ payload: { userId: 1 } });

  assert.deepEqual(
    generator.next().value,
    call(api.fetchUser, 1),
    'should call API'
  );

  const mockUser = { id: 1, name: 'John' };
  assert.deepEqual(
    generator.next(mockUser).value,
    put({ type: 'FETCH_USER_SUCCESS', user: mockUser }),
    'should dispatch success action'
  );

  assert.end();
});

Интеграция с React

После настройки саг они автоматически будут обрабатывать действия, диспатчимые из React-компонентов:

import { useDispatch } from 'react-redux';

const UserComponent = () => {
  const dispatch = useDispatch();

  const handleClick = () => {
    dispatch({ type: 'FETCH_USER_REQUEST', payload: { userId: 123 } });
  };

  return <button onClick={handleClick}>Load User</button>;
};

Оптимизация

  1. Разделение кода (Code Splitting): Динамически загружайте саги с redux-saga и React.lazy.
  2. Группировка действий: Используйте takeEvery или takeLatest для схожих действий.
  3. Использование takeLeading: Если нужно обрабатывать только первое действие, игнорируя последующие до завершения.

Частые ошибки

  1. Забывают запустить сагу: sagaMiddleware.run(rootSaga) должен быть вызван.
  2. Бесконечные циклы: Неправильное использование while (true) без условий выхода.
  3. Утечки памяти: Незавершенные задачи при размонтировании компонента.

Резюмируем

  1. Установите redux-saga и создайте middleware.
  2. Определите рабочие и наблюдающие саги.
  3. Подключите middleware к Redux-сторе.
  4. Запустите корневую сагу.
  5. Используйте эффекты для управления сайд-эффектами.
  6. Обрабатывайте ошибки и отменяйте задачи при необходимости.

Для сложных проектов Sagas — мощный инструмент для управления асинхронной логикой.