Вы не
Но ... вы должны использовать Redx-сагу :)
Ответ Дана Абрамова верен, redux-thunkно я расскажу немного больше о сундук-реду, который очень похож, но более силен.
Повелительный указ декларативный
- ДОМ : JQuery является обязательным / React является декларативным
- Монады : IO является обязательным / Free является декларативным
- Redux эффекты :
redux-thunkобязательно / redux-sagaдекларативно
Когда у вас в руках есть Thunk, например IO-монада или обещание, вы не можете легко знать, что он будет делать после выполнения. Единственный способ проверить thunk - это выполнить его и высмеять диспетчера (или весь внешний мир, если он взаимодействует с большим количеством материала ...).
Если вы используете макеты, то вы не занимаетесь функциональным программированием.
Сквозь призму побочных эффектов макеты - это признак того, что ваш код нечист, и, на взгляд функционального программиста, доказательство того, что что-то не так. Вместо того, чтобы загружать библиотеку, чтобы помочь нам проверить, не поврежден ли айсберг, мы должны плыть вокруг него. Один хардкорный TDD / Java парень как-то спросил меня, как ты делаешь насмешки в Clojure. Ответ, мы обычно не делаем. Обычно мы видим в этом знак того, что нам нужно реорганизовать наш код.
Источник
Саги (в том виде, в redux-sagaкаком они были реализованы ) являются декларативными и, подобно компонентам Free monad или React, их гораздо проще тестировать без всяких насмешек.
Смотрите также эту статью :
в современных FP мы не должны писать программы - мы должны писать описания программ, которые мы можем затем анализировать, трансформировать и интерпретировать по желанию.
(На самом деле Redux-сага похожа на гибрид: поток обязателен, но эффекты декларативны)
Путаница: действия / события / команды ...
В мире внешнего интерфейса существует большая путаница в отношении того, как могут быть связаны некоторые базовые концепции, такие как CQRS / EventSourcing и Flux / Redux, главным образом потому, что в Flux мы используем термин «действие», который иногда может представлять как императивный код ( LOAD_USER), так и события ( USER_LOADED). Я считаю, что, как и в случае с источником событий, вы должны только отправлять события.
Использование саг на практике
Представьте себе приложение со ссылкой на профиль пользователя. Идиоматический способ справиться с этим с каждым промежуточным программным обеспечением:
redux-thunk
<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
);
}
redux-saga
<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>
function* loadUserProfileOnNameClick() {
yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}
function* fetchUser(action) {
try {
const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
yield put({ type: 'USER_PROFILE_LOADED', userProfile })
}
catch(err) {
yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
}
}
Эта сага переводится как:
каждый раз, когда по имени пользователя нажимают, выбирают профиль пользователя и затем отправляют событие с загруженным профилем.
Как видите, есть некоторые преимущества redux-saga.
Использование takeLatestразрешений для выражения того, что вас интересует только получение данных о последнем щелчке имени пользователя (устранение проблем параллелизма в случае, если пользователь очень быстро нажимает на большое количество имен пользователей). Такого рода вещи сложны с громилой. Вы могли бы использовать, takeEveryесли вы не хотите этого поведения.
Вы держите создателей действий в чистоте. Обратите внимание, что все еще полезно сохранять actionCreators (в сагах putи компонентах dispatch), поскольку это может помочь вам добавить проверку действия (assertions / flow / typescript) в будущем.
Ваш код становится намного более тестируемым, так как эффекты декларативны
Вам больше не нужно инициировать вызовы, похожие на rpc actions.loadUser(). Ваш пользовательский интерфейс просто должен отправить то, что произошло. Мы только запускаем события (всегда в прошедшем времени!), А не действия больше. Это означает, что вы можете создавать развязанные «утки» или ограниченные контексты и что сага может выступать в качестве связующего звена между этими модульными компонентами.
Это означает, что вашими представлениями легче управлять, потому что им больше не нужно содержать слой перевода между тем, что произошло, и тем, что должно произойти как результат.
Например, представьте бесконечный вид прокрутки. CONTAINER_SCROLLEDможет привести к тому NEXT_PAGE_LOADED, но действительно ли прокручиваемый контейнер несет ответственность за решение, следует ли нам загружать другую страницу? Затем он должен знать о более сложных вещах, таких как, была ли успешно загружена последняя страница, или уже есть страница, которая пытается загрузить, или нет больше элементов для загрузки? Я так не думаю: для максимального повторного использования прокручиваемый контейнер должен просто описывать, что он был прокручен. Загрузка страницы является «бизнес-эффектом» этого свитка
Кто-то может возразить, что генераторы могут скрывать состояние за пределами хранилища избыточных данных с помощью локальных переменных, но если вы начнете организовывать сложные вещи внутри thunks, запуская таймеры и т.д., у вас все равно будет та же проблема. И есть selectэффект, который теперь позволяет получить состояние из вашего магазина Redux.
Sagas позволяет путешествовать во времени, а также обеспечивает сложную регистрацию потоков и инструменты разработки, над которыми в настоящее время ведутся работы. Вот некоторая простая асинхронная регистрация потока, которая уже реализована:

Развязка
Саги не только заменяют редуксных сундуков. Они поступают из бэкэнда / распределенных систем / источников событий.
Это очень распространенное заблуждение, что саги только здесь, чтобы заменить ваши редуксные гирлянды лучшей тестируемостью. На самом деле, это всего лишь деталь реализации Redx-саги. Использование декларативных эффектов лучше, чем thunk для тестируемости, но шаблон саги может быть реализован поверх императивного или декларативного кода.
Во-первых, сага - это часть программного обеспечения, которая позволяет координировать длительные транзакции (конечная согласованность) и транзакции в разных ограниченных контекстах (жаргон на основе доменного дизайна).
Чтобы упростить это для внешнего мира, представьте, что есть widget1 и widget2. Когда нажата какая-то кнопка на widget1, это должно повлиять на widget2. Вместо того, чтобы соединить 2 виджета вместе (т.е. widget1 отправляет действие, которое нацелено на widget2), widget1 отправляет только то, что была нажата его кнопка. Затем сага прослушивает нажатие этой кнопки, а затем обновляет widget2, отправляя новое событие, о котором известно widget2.
Это добавляет уровень косвенности, который не нужен для простых приложений, но упрощает масштабирование сложных приложений. Теперь вы можете публиковать widget1 и widget2 в разных репозиториях npm, чтобы им никогда не приходилось узнавать друг о друге, не предоставляя им общий глобальный реестр действий. 2 виджета теперь являются ограниченными контекстами, которые могут жить отдельно. Они не нуждаются друг в друге, чтобы быть последовательными и могут быть повторно использованы в других приложениях. Сага является связующим звеном между двумя виджетами, которые значимым образом координируют их для вашего бизнеса.
Несколько хороших статей о том, как структурировать ваше приложение Redux, в котором вы можете использовать Redux-saga по причинам разделения:
Конкретный вариант использования: система уведомлений
Я хочу, чтобы мои компоненты могли запускать отображение уведомлений в приложении. Но я не хочу, чтобы мои компоненты были тесно связаны с системой уведомлений, которая имеет свои собственные бизнес-правила (макс. 3 уведомления, отображаемые одновременно, очередь уведомлений, время отображения 4 секунды и т. Д.).
Я не хочу, чтобы мои компоненты JSX решали, когда будет отображаться / скрываться уведомление. Я просто даю ему возможность запросить уведомление и оставить сложные правила внутри саги. Подобные вещи довольно сложно реализовать с помощью благих обещаний.

Я описал здесь, как это можно сделать с помощью саги
Почему это называется сага?
Термин сага происходит из бэкэнд-мира. Я первоначально представил Yassine (автора Redux-saga) этому термину в длительной дискуссии .
Первоначально этот термин был введен в статье. Предполагалось, что шаблон saga будет использоваться для обработки возможной согласованности в распределенных транзакциях, но его использование было расширено до более широкого определения бэкэнд-разработчиками, так что теперь оно также охватывает «менеджер процессов». шаблон (каким-то образом оригинальный шаблон саги является специализированной формой менеджера процессов).
Сегодня термин «сага» сбивает с толку, поскольку он может описывать 2 разные вещи. Поскольку он используется в redux-saga, он описывает не способ обработки распределенных транзакций, а способ координации действий в вашем приложении. redux-sagaтакже можно было бы назвать redux-process-manager.
Смотрите также:
альтернативы
Если вам не нравится идея использования генераторов, но вас интересует шаблон саги и его свойства развязки, вы также можете добиться того же с помощью redux-observable, которое использует имя epicдля описания точно такого же шаблона, но с RxJS. Если вы уже знакомы с Rx, вы будете чувствовать себя как дома.
const loadUserProfileOnNameClickEpic = action$ =>
action$.ofType('USER_NAME_CLICKED')
.switchMap(action =>
Observable.ajax(`http://data.com/${action.payload.userId}`)
.map(userProfile => ({
type: 'USER_PROFILE_LOADED',
userProfile
}))
.catch(err => Observable.of({
type: 'USER_PROFILE_LOAD_FAILED',
err
}))
);
Некоторые полезные ресурсы редукса-саги
2017 советует
- Не злоупотребляйте Redux-сагой только ради ее использования. Только тестируемые вызовы API не стоят того.
- Не удаляйте thunks из вашего проекта для самых простых случаев.
- Не стесняйтесь отправлять Thunks,
yield put(someActionThunk)если это имеет смысл.
Если вы боитесь использовать Redux-saga (или Redux-observable), но вам нужен только шаблон развязки, проверьте redux-dispatch-subscribe : он позволяет прослушивать диспетчеризацию и запускать новые диспетчеры в приемнике.
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});