- Когда использовать Redux Saga
- Основной пример использования Redux Saga
- Как это работает за кулисами
- Основные Помощники
- возьмите каждый()
- возьмите последнюю версию()
- взять()
- положить()
- вызов()
- Запуск эффектов параллельно
- все()
- гонка ()
Когда использовать Redux Saga
В приложении, использующем Redux, при запуске действия что-то меняется в состоянии приложения.
Когда это произойдет, вам может потребоваться сделать что-то, что вытекает из этого изменения состояния.
Например, вы можете захотеть:
- сделайте HTTP-вызов на сервер
- отправить событие WebSocket
- извлеките некоторые данные с сервера GraphQL
- сохраните что-нибудь в кэше или локальном хранилище браузера
…ты уловил идею.
Это все вещи, которые на самом деле не связаны с состоянием приложения или являются асинхронными, и вам нужно переместить их в место, отличное от ваших действий или редукторов (хотя технически вы могли бы , это не лучший способ иметь чистую кодовую базу).
Войдите в Redux Saga, промежуточное программное обеспечение Redux, помогающее вам с побочными эффектами.
Основной пример использования Redux Saga
Чтобы не погружаться в слишком много теории, прежде чем показывать какой-то реальный код, я кратко расскажу, как я решил проблему, с которой столкнулся при создании примера приложения.
В чате, когда пользователь пишет сообщение, я сразу же показываю сообщение на экране, чтобы обеспечить быструю обратную связь. Это делается с помощью действия Redux:
const addMessage = (message, author) => ({
type: 'ADD_MESSAGE',
message,
author
})и состояние изменяется через редуктор:
const messages = (state = [], action) => {
switch (action.type) {
case 'ADD_MESSAGE':
return state.concat([{
message: action.message,
author: action.author
}])
default:
return state
}
}Вы инициализируете Redux Saga, сначала импортируя его, а затем применяя saga в качестве промежуточного программного обеспечения в хранилище Redux:
//... import createSagaMiddleware from 'redux-saga' //...
Затем мы создаем промежуточное программное обеспечение и применяем его к нашему недавно созданному магазину Redux:
const sagaMiddleware = createSagaMiddleware() const store = createStore( reducers, applyMiddleware(sagaMiddleware) )
Последний шаг – запуск саги. Мы импортируем его и передаем в метод run промежуточного программного обеспечения:
import handleNewMessage from './sagas' //... sagaMiddleware.run(handleNewMessage)
Нам просто нужно написать сагу в ./сагах/index.js :
import { takeEvery } from 'redux-saga/effects'
const handleNewMessage = function* handleNewMessage(params) {
const socket = new WebSocket('ws://localhost:8989')
yield takeEvery('ADD_MESSAGE', (action) => {
socket.send(JSON.stringify(action))
})
}
export default handleNewMessageЭтот код означает следующее: каждый раз, когда срабатывает действие ADD_MESSAGE , мы отправляем сообщение на сервер WebSockets , который в этом случае отвечает на localhost:8989 .
Обратите внимание на использование функции* , которая не является обычной функцией, а генератором .
Как это работает за кулисами
Будучи промежуточным программным обеспечением Redux, Redux Saga может перехватывать действия Redux и внедрять свои собственные функции.
Есть несколько концепций, которые нужно понять, и вот основные ключевые слова, которые вы захотите запомнить в целом: сага , генератор , промежуточное программное обеспечение , обещание , пауза , возобновить , эффект , отправка , действие , выполнено , решено , уступать , уступил .
Сага – это некая “история”, которая реагирует на эффект , который вызывает ваш код. Это может содержать одну из вещей, о которых мы говорили ранее, например HTTP-запрос или какую-либо процедуру, которая сохраняется в кэше.
Мы создаем промежуточное программное обеспечение со списком саг для запуска, которое может быть одним или несколькими, и подключаем это промежуточное программное обеспечение к магазину Redux.
/|сага является генератором функцией. Когда обещание выполняется и выполняется , промежуточное программное обеспечение приостанавливается |сага до тех пор, пока обещание не будет выполнено .
Как только обещание будет выполнено промежуточное программное обеспечение возобновится сага, пока не будет найдено следующее выход заявление, и там оно приостановлено снова до его обещания |/разрешает .
Внутри кода saga вы будете генерировать эффекты , используя несколько специальных вспомогательных функций, предоставляемых пакетом redux-saga . Для начала мы можем перечислить:
возьмите каждый()возьмите последнюю версию()взять()вызов()положить()
Когда эффект выполняется, сага приостанавливается до тех пор, пока эффект не будет выполнен .
Например:
import { takeEvery } from 'redux-saga/effects'
const handleNewMessage = function* handleNewMessage(params) {
const socket = new WebSocket('ws://localhost:8989')
yield takeEvery('ADD_MESSAGE', (action) => {
socket.send(JSON.stringify(action))
})
}
export default handleNewMessageКогда промежуточное программное обеспечение выполняет handleNewMessage сагу, оно останавливается на выходе, принимает каждую инструкцию и ждет ( асинхронно , конечно) до тех пор, пока действие ADD_MESSAGE не будет отправлено . Затем он выполняет обратный вызов, и сага может возобновиться .
Основные Помощники
Помощники – это абстракции поверх низкоуровневых API saga.
Давайте представим самых простых помощников, которых вы можете использовать для запуска своих эффектов:
возьмите каждый()возьмите последнюю версию()взять()положить()вызов()
возьмите каждый()
возьмите каждый() , используемый в некоторых примерах, является одним из таких помощников.
В коде:
import { takeEvery } from 'redux-saga/effects'
function* watchMessages() {
yield takeEvery('ADD_MESSAGE', postMessageToServer)
}Генератор WATCHMESSAGES приостанавливается до тех пор, пока не сработает действие ADD_MESSAGE , и каждый раз, когда он срабатывает, он будет вызывать функцию postMessageToServer бесконечно и одновременно (нет необходимости в postMessageToServer для завершения его выполнения до запуска нового раз)
возьмите последнюю версию()
Еще одним популярным помощником является возьмите последнюю() , которая очень похожа на возьмите каждую() но позволяет одновременно запускать только один обработчик функций, избегая параллелизма. Если другое действие будет запущено, когда обработчик все еще работает, оно отменит его и запустится снова с последними доступными данными.
Как и в случае с take Every() , генератор никогда не останавливается и продолжает запускать эффект, когда происходит указанное действие.
взять()
take() отличается тем, что он ожидает только один раз. Когда происходит ожидаемое действие, обещание выполняется, и итератор возобновляется, чтобы он мог перейти к следующему набору инструкций.
положить()
Отправляет действие в хранилище изменений. Вместо того, чтобы передавать в хранилище Redux или действие отправки в saga, вы можете просто использовать put() :
yield put({ type: 'INCREMENT' })
yield put({ type: "USER_FETCH_SUCCEEDED", data: data })который возвращает простой объект, который вы можете легко проверить в своих тестах (подробнее о тестировании позже).
вызов()
Если вы хотите вызвать какую-либо функцию в саге, вы можете сделать это, используя простой вызов функции, который возвращает обещание:
delay(1000)
но это не очень хорошо сочетается с тестами. Вместо этого вызов() позволяет завершить вызов этой функции и возвращает объект, который можно легко проверить:
call(delay, 1000)
возвращается
{ CALL: {fn: delay, args: [1000]}}Запуск эффектов параллельно
Параллельное выполнение эффектов возможно с использованием all() и race() , которые сильно отличаются в том, что они делают.
все()
Если вы напишете
import { call } from 'redux-saga/effects'
const todos = yield call(fetch, '/api/todos')
const user = yield call(fetch, '/api/user')второй вызов fetch() не будет выполнен до тех пор, пока первый не завершится успешно.
Чтобы выполнить их параллельно, оберните их в все() :
import { all, call } from 'redux-saga/effects'
const [todos, user] = yield all([
call(fetch, '/api/todos'),
call(fetch, '/api/user')
])все() не будут решены, пока оба не вызовут() не вернутся.
гонка ()
race() отличается от all() тем, что не ждет, пока все вызовы помощников вернутся. Он просто ждет, когда кто-нибудь вернется, и все готово.
Это гонка, чтобы увидеть, кто из них финиширует первым, а затем мы забываем о других участниках.
Обычно он используется для отмены фоновой задачи, которая выполняется вечно, пока что-то не произойдет:
import { race, call, take } from 'redux-saga/effects'
function* someBackgroundTask() {
while(1) {
//...
}
}
yield race([
bgTask: call(someBackgroundTask),
cancel: take('CANCEL_TASK')
])когда выполняется действие CANCEL_TASK , мы останавливаем другую задачу, которая в противном случае выполнялась бы вечно.
Оригинал: “https://flaviocopes.com/redux-saga/”