Привет,
В этом руководстве мы рассмотрим Redux и использование его промежуточного ПО. Посмотрим, какое эффективное решение предлагает redux-saga. И в конце мы создадим небольшое приложение, используя redux-saga, и сравним его с промежуточным программным обеспечением Redux-thunk.

Я предполагаю, что вы знакомы с фреймворком ReactJS. Итак, прежде чем перейти к теме, во-первых, нам нужно знать, что такое Redux и почему требуется промежуточное программное обеспечение и какое элегантное решение функции генератора предоставляют нам в ES6 javascript во время сложных сценариев разработки приложения.

Что такое Редукс?

Redux — это контейнер с глобальным состоянием приложений JavaScript, которые одинаково ведут себя в клиентских, серверных и собственных средах и легко тестируются. Его можно использовать вместе с React или любой другой библиотекой представлений, поскольку он крошечный (2 КБ, включая зависимости) и имеет большую экосистему доступных дополнений.

Одной из основных концепций Redux являются редукторы:

  1. Глобальное состояние вашего приложения разработано с использованием дерева объектов внутри одного хранилища, которое указывает на функцию сокращения одного корня.
  2. Единственный способ изменить это глобальное дерево состояний – создать действие, объект, описывающий произошедшее, и отправить в магазин через действия.
  3. Чтобы обновить состояние в ответ на действие, вы пишете чистые функции reducer, которые вычисляют новое состояние на основе старого состояния и действия и сохраняют его в корневом каталоге. редуктор, называемый хранилищем приложения.

Что такое побочные эффекты?

Побочные эффекты — это в основном любой код, который выполняется асинхронно и влияет на что-то, выходящее за рамки выполняемой функции. Это включает в себя:

  • Асинхронные запросы API к серверной службе
  • Звонки в службу аутентификации
  • Ошибка отслеживания звонков в Sentry

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

Почему промежуточное ПО требуется в Redux?

Действия Redux отправляются синхронно, что является проблемой для любого нетривиального приложения, которому необходимо взаимодействовать с внешним API или выполнять побочные эффекты или какую-то другую сложную логику/рабочий процесс?

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

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

Редукс-Сага

Что такое сага?

По сути, saga выполняются с использованием функций-генераторов, которые передают объекты промежуточному программному обеспечению redux-saga.

Что такое Генераторная функция?

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

Функция генератора работает по-другому, так как она может быть приостановлена в середине, один или несколько раз, а затем возобновлена ​​позже, что позволяет запускать другие коды в течение этих периодов паузы. Красиво, да?

Внутри тела функции генератора нам нужно использовать новое ключевое слово yield, чтобы приостановить функцию внутри самой функции. После того как генератор приостановил свою работу, его нельзя возобновить самостоятельно или приостановить извне. Для перезапуска генератора требуется внешнее управление.

Базовый синтаксис функции-генератора определяется объявлением function* (ключевое слово function, за которым следует звездочка).

function* GeneratorFunction() {
  // code here
};

Он не может быть создан сам по себе, вместо этого нам нужно создать объект функции-генератора.

const it = GeneratorFunction();

Давайте посмотрим пример:

// define
function* printMessage() { 
   yield "This will be printed."; 
   yield "Now, this will be printed.";
}
// init
const gen = printMessage(); // use
console.log(gen.next()); 
// {value: "This will be printed.", done: false}
console.log(gen.next()); 
// {value: "Now, this will be printed.", done: false}
console.log(gen.next()); 
// {value: undefined, done: true}

Как видите, функция генератора возвращает объект итератор, используя next() метод, который имеет свойство value, имеющее полученное значение, и свойство done, указывающее, выдал ли генератор свое последнее значение, т.к. логическое значение.

Это может упростить написание и понимание асинхронного кода. Например, вместо этого:

fetchData(url).then(resp => {
    console.log(resp)
}).catch((e) => {
    console.log("Something went wrong.");
});

С генераторами вы можете написать так:

let resp = yield fetchData(url);
console.log(resp);

Сага Redux

Он предоставляет вспомогательные функции для выполнения асинхронных действий. Yield — это встроенная функция, позволяющая последовательно использовать функции генератора. При использовании в Javascript функции-генераторы позволяют получить все значения из вложенных функций.
(Обратите внимание, что функции-генераторы могут вызывать из них другие функции-генераторы.)

import { takeEvery } from 'redux-saga/effects';
import Api from './path/to/api';
function* watchFetchUsers() {
   yield takeEvery('USERS_REQUESTED', fetchUsers);
}
function* fetchUsers() {
 try {
  const data = yield Api.fetch('/users');
  console.log(data);
  } catch(error) {
  console.log(error);
 }
}

В приведенном выше примере мы вызвали Api.fetch непосредственно из Генератора, который сначала будет оцениваться, а затем результат передается вызывающей стороне.

Api.fetch('/data') инициирует запрос AJAX и возвращает Promise, который разрешается с разрешенным ответом, что означает, что запрос AJAX будет выполнен немедленно.

Здесь у нас есть наблюдатель takeEvery, который будет искать несколько запросов и запускать несколько экземпляров рабочей саги. Другими словами, он обрабатывает параллелизм для вас. Этот дополнительный уровень может дать нам больше гибкости для работы со сложными сценариями. Но это может быть ненужным для большинства приложений.

ВЫЗОВ

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

Возвращаемые объекты называются эффектами, которые выглядят следующим образом:

// Effect -> call the function Api.fetch with `./users` as argument
{
  CALL: {
    fn: Api.fetch,
    args: ['./users']
  }
}

(Точно так же, как создатели действий в Redux, которые также создают простой объект, описывающий действие, которое будет выполнено Магазином.)

Таким образом, приведенный выше пример можно реализовать в redux-saga следующим образом:

import { call } from 'redux-saga/effects'
function* fetchUsers() {
  const products = yield call(Api.fetch, '/users')
  // ...
}

То же самое происходит с методом put. Вместо отправки действия внутри генератора put возвращает объект с инструкциями для промежуточного программного обеспечения для отправки действия.

Итак, Redux-saga использует декларативные вызовы, отличные от императивных. Различия между этими двумя смотрите здесь.

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

Мы можем протестировать всю логику внутри саги, перебирая Генератор и выполняя deepEqualilty проверку последовательно полученных значений. Это реальная выгода, так как не будет сложных асинхронных операций, и вы сможете детально протестировать их сложную операционную логику.

import { call } from 'redux-saga/effects'
import Api from '...'
const iterator = fetchUsers()
// expects a call instruction
assert.deepEqual(
  iterator.next().value,
  call(Api.fetch, '/users'),
  "fetchUsers should yield an Effect call(Api.fetch, './users')"
)

Теперь, помимо call, put и takeEvery, у нас есть гораздо больше возможностей влиять на создателей. Проверьте их в документации.

Теперь давайте создадим приложение, которое будет получать случайные изображения при каждом нажатии кнопки:

Использование Redux-преобразователя

результат будет:

Теперь с помощью Redux-saga:

Конечный результат будет таким:

Объяснение:

При нажатии кнопки происходит вот что:

  1. Действие FETCHED_IMAGE отправляется
  2. Наша сага об наблюдателях (watchFetchImage) принимает отправленное действие и вызывает сагу об исполнителях (fetchImageAsync)
  3. Отправляется действие для отображения индикатора загрузки
  4. Вызов API выполняется
  5. Отправлено действие для обновления состояния (успешно или неудачно)

Если вы считаете, что некоторые уровни косвенности и небольшая дополнительная работа того стоят, redux-saga может дать вам больше контроля для функциональной обработки побочных эффектов.

Вывод:

Как вы видите, сага Redux может решить наши проблемы реализации асинхронной операции с создателями действий и сагами, от объяснения того, что такое функции генератора и что такое избыточность, до создания небольшого приложения-генератора изображений с использованием промежуточного программного обеспечения Redux-thunk и Redux-saga.

Redux не дает нам решения для обработки побочных эффектов. При принятии решения о том, какой подход выбрать, вы должны проанализировать сложность вашего приложения. Я бы предложил сначала принять самое простое решение.

Наслаждайтесь и счастливого обучения.👍🏻
Комментируйте, если у вас есть какие-либо вопросы.

Ссылки:
1. Image API: https://unsplash.com/
2. ReactJs: https://reactjs.org/
3. Redux-сага: https://redux-saga.js.org/