Рубрики
Без рубрики

Введение в сагу Redux

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

  • Когда использовать 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/”