Вы не
Но ... вы должны использовать 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' });
}
});