Введение в Redux Saga

Redux Saga - это библиотека, используемая для обработки побочных эффектов в Redux. Когда вы запускаете действие, что-то меняется в состоянии приложения, и вам может потребоваться сделать что-то, вытекающее из этого изменения состояния.

Когда использовать Redux Saga

В приложении, использующемRedux, когда вы запускаете действие, что-то меняется в состоянии приложения.

В этом случае вам может потребоваться сделать что-то, вытекающее из этого изменения состояния.

Например, вы можете захотеть:

  • сделать HTTP-вызов на сервер
  • отправить событие WebSocket
  • получить данные изGraphQLсервер
  • сохранить что-нибудь в кеш или локальное хранилище браузера

… Вы поняли идею.

Это все вещи, которые на самом деле не связаны с состоянием приложения или являются асинхронными, и вам нужно переместить их в место, отличное от ваших действий или редукторов (в то время как вы техническимог, это не лучший способ иметь чистую кодовую базу).

Представляем Redux Saga, промежуточное программное обеспечение Redux, которое поможет вам справиться с побочными эффектами.

Базовый пример использования Redux Saga

Чтобы не углубляться в теорию, прежде чем показывать реальный код, я кратко расскажу, как я решил проблему, с которой столкнулся при создании примера приложения.

В чате, когда пользователь пишет сообщение, я немедленно показываю это сообщение на экране, чтобы обеспечить оперативную обратную связь. Это делается черезRedux Action:

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, сначала импортируя его, а затем применяясагав качестве промежуточного программного обеспечения для Redux Store:

//...
import createSagaMiddleware from 'redux-saga'
//...

Затем мы создаем промежуточное ПО и применяем его к нашему недавно созданному Redux Store:

const sagaMiddleware = createSagaMiddleware()

const store = createStore( reducers, applyMiddleware(sagaMiddleware) )

Последний шаг - запуск саги. Мы импортируем его и передаем методу run промежуточного программного обеспечения:

import handleNewMessage from './sagas'
//...
sagaMiddleware.run(handleNewMessage)

Нам просто нужно написать сагу в./sagas/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.

Обратите внимание на использованиеfunction*, что не является нормальной функцией, агенератор.

Как это работает за кадром

БытьReduxПромежуточное ПО, Redux Saga может перехватывать действия Redux и внедрять свои собственные функции.

Есть несколько концепций, которые нужно усвоить, и вот основные ключевые слова, которые вы захотите вонзить в свою голову вместе:сага,генератор,промежуточное ПО,обещать,Пауза,продолжить,эффект,отправлять,действие,выполнено,решено,урожай,уступил.

Асагаэто некая «история», которая реагирует наэффектчто ваш код вызывает. Это может содержать что-то из того, о чем мы говорили раньше, например, HTTP-запрос или некоторую процедуру, которая сохраняется в кеш.

Мы создаемпромежуточное ПОсо спискомсагиto run, которых может быть один или несколько, и мы подключаем это промежуточное ПО к хранилищу Redux.

Асагаэтогенераторфункция. Когдаобещатьзапущен иуступил, промежуточное ПОприостанавливаетвсагадообещатьявляетсярешено.

Однаждыобещатьявляетсярешенопромежуточное ПОвозобновляетсясага, до следующегоурожайзаявление найдено, и вот оноприостановленныйснова, пока егообещать решает.

Внутри кода саги вы создадитепоследствияиспользуя несколько специальных вспомогательных функций, предоставляемыхredux-sagaупаковка. Для начала мы можем перечислить:

  • takeEvery()
  • takeLatest()
  • take()
  • call()
  • put()

Когдаэффектвыполняется,сагаявляетсяприостановленодоэффектявляетсявыполнено.

Например:

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сага, этоостанавливаетсянаyield takeEveryинструкция иждет(асинхронно, конечно) до тех пор, покаADD_MESSAGEдействиеотправлен. Затем он выполняет обратный вызов, исагаможетпродолжить.

Базовые помощники

Помощники - это абстракции поверх низкоуровневых API-интерфейсов саги.

Давайте познакомимся с самыми основными помощниками, которые вы можете использовать для запуска ваших эффектов:

  • takeEvery()
  • takeLatest()
  • take()
  • put()
  • call()

takeEvery()

takeEvery(), используемый в некоторых примерах, является одним из таких помощников.

В коде:

import { takeEvery } from 'redux-saga/effects'

function* watchMessages() { yield takeEvery(‘ADD_MESSAGE’, postMessageToServer) }

ВwatchMessagesгенератор останавливается до тех пор, покаADD_MESSAGEбоевые пожары, икаждый разон срабатывает, он вызоветpostMessageToServerфункция, бесконечно и одновременно (нет необходимостиpostMessageToServerчтобы завершить его выполнение до того, как можно будет запустить новый раз)

takeLatest()

Еще один популярный помощник -takeLatest(), что очень похоже наtakeEvery()но позволяет одновременно запускать только один обработчик функции, избегая параллелизма. Если другое действие запускается, когда обработчик все еще работает, он отменяет его и запускается снова с последними доступными данными.

Как и в случае сtakeEvery(), генератор никогда не останавливается и продолжает запускать эффект при выполнении указанного действия.

take()

take()отличается тем, что ждет только один раз. Когда происходит ожидаемое действие, обещание разрешается и итератор возобновляет работу, поэтому он может перейти к следующему набору инструкций.

put()

Отправляет действие в магазин Redux. Вместо передачи в хранилище Redux или действия отправки в сагу вы можете просто использоватьput():

yield put({ type: 'INCREMENT' })
yield put({ type: "USER_FETCH_SUCCEEDED", data: data })

который возвращает простой объект, который вы можете легко проверить в своих тестах (подробнее о тестировании позже).

call()

Если вы хотите вызвать какую-либо функцию в саге, вы можете сделать это, используя простой вызов функции, возвращающий обещание:

delay(1000)

но это не очень хорошо с тестами. Вместо,call()позволяет обернуть этот вызов функции и вернуть объект, который можно легко проверить:

call(delay, 1000)

возвращается

{ CALL: {fn: delay, args: [1000]}}

Параллельное выполнение эффектов

Параллельное выполнение эффектов возможно с помощьюall()иrace(), которые сильно различаются по своему назначению.

all()

Если вы напишете

import { call } from 'redux-saga/effects'

const todos = yield call(fetch, ‘/api/todos’) const user = yield call(fetch, ‘/api/user’)

второйfetch()вызов не будет выполнен, пока первый не завершится успешно.

Чтобы выполнить их параллельно, оберните их вall():

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

const [todos, user] = yield all([ call(fetch, ‘/api/todos’), call(fetch, ‘/api/user’) ])

all()не будет решен, пока обаcall()возвращаться.

race()

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действие, мы останавливаем другую задачу, которая в противном случае выполнялась бы вечно.

Скачать мою бесплатнуюСправочник по React


Больше руководств по реакции: