Redux Saga — это middleware для Redux, который позволяет управлять сайд-эффектами (side effects, побочными эффектами) с помощью генераторов (generators). Чаще всего используется для асинхронных операций, таких как HTTP-запросы.
Сначала необходимо установить необходимые пакеты:
npm install redux-saga
# или
yarn add redux-saga
Пример простой саги для обработки 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;
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),
]);
}
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 });
}
}
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(); // Отмена текущей задачи
}
}
}
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-компонентов:
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>;
};
redux-saga
и React.lazy
.takeEvery
или takeLatest
для схожих действий.takeLeading
: Если нужно обрабатывать только первое действие, игнорируя последующие до завершения.sagaMiddleware.run(rootSaga)
должен быть вызван.while (true)
без условий выхода.redux-saga
и создайте middleware.Для сложных проектов Sagas — мощный инструмент для управления асинхронной логикой.