ARC - Упрощение асинхронных запросов в приложениях Redux

TL-DR: Цель этой статьи - подробно объяснить, как работает Arc. Если вы ищете только инструкции о том, как начать работу с Arc, вы можете перейти непосредственно к README репозитория или его документации. Однако всегда полезно знать, с какими инструментами вы работаете.

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

Я работаю с приложениями redux почти два года и пробовал разные подходы для обработки асинхронных запросов. Из-за боли и разочарований, которые у меня были в этом процессе, возникла идея создать библиотеку, которая абстрагирует раздражающие части. Что-то, что предотвращало повторение кода, но в то же время было гибким и масштабируемым, что позволяло пользователю тратить свое время там, где это действительно важно. Так родилась Arc.

Arc - это библиотека размером 2 КБ, полностью свободная от зависимостей и на 100% покрытая тестами.

Мы можем разделить Arc на две независимо друг от друга части:

  • Типы и создатели фабрики.
  • AsyncMiddleware.

Основные концепции

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

Создатель действий

Создатель действий - это функция, которая используется для создания действий. Создатель действий позволяет нам централизовать создание определенного типа действия, что помогает нам обеспечить единообразие в приложении. Ниже вы можете увидеть пример создателя действия:

Согласно вышеупомянутому создателю действия, чтобы получить действие ADD_TODO , нам просто нужно выполнить функцию addTodo, предоставляя описание и дату, как показано в следующем примере:

Асинхронные действия

асинхронное действие - это действие, описывающее запрос. Ниже вы можете увидеть пример асинхронного действия из Arc:

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

Еще мы можем заметить, что у нас есть объект meta, у которого есть атрибут url и другой атрибут method. url будет конечной точкой запроса, а метод будет тем, который использовался для выполнения запроса (получить, опубликовать, поместить, удалить).

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

Если вам нужно отправить в действие какие-либо дополнительные данные, вы можете включить их в мета-объект.

{
  ...
  meta:{
    url,
    method,
    anyOtherParam,
  },
}

Arc следует шаблону стандартного действия потока, чтобы определить свое действие. Их README очень прост и может развеять возможные сомнения относительно решений о формате действий Arc.

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

ПО промежуточного слоя

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

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

ПО промежуточного слоя в Redux - это функция, которая должна соблюдать следующую сигнатуру:

В Redux промежуточное ПО выполняется каждый раз, когда в приложении отправляется действие. Внутри него у нас есть доступ к объекту store, который содержит методы getState и dispatch, которые можно использовать для взаимодействия с состоянием redux. .

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

Наконец, мы получаем действие, которое было отправлено.

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

const middleware = store => next => action => {
  const newAction = {
    ...action,
    payload: anyNewPayload,
  };
  next(newAction);
};

Чтобы перехватить действие, не вызывайте метод next:

const TYPE_TO_INTERCEPT = 'X';
const middleware = store => next => action => {
  if (action.type === TYPE_TO_INTERCEPT) {
    return;
  }
  next(newAction);
};

В приведенном выше примере мы перехватываем все действия типа X.

Вы видите возможности, которые дает нам промежуточное ПО? Нет? Не волнуйтесь, дальше все станет ясно!

Асинхронное промежуточное ПО

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

Ниже вы можете увидеть пример асинхронного промежуточного программного обеспечения.

В приведенном выше примере используется axios, но в Arc вы можете выбрать любую библиотеку, которая вам нравится, или даже реализовать запрос на ванильном JS.

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

Если тип действия является массивом, мы предполагаем, что рассматриваемое действие является асинхронным действием. При этом мы извлекаем как строки, requestType и responseType, так и объекты payload и meta:

const [requestType, responseType] = action.type;
const { meta, payload } = action;

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

store.dispatch({ type: requestType, payload, meta });

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

axios[meta.method](meta.url, payload)

После выполнения запроса мы отправляем второе действие, имеющее responseType в качестве типа, а также meta.

В случае успеха результатом запроса будет action.payload:

store.dispatch({
  type: responseType,
  payload: response,
  meta,
});

В случае ошибки action.payload становится ошибкой запроса, и мы устанавливаем для action.error значение true:

store.dispatch({
  type: responseType,
  payload: error,
  error: true,
  meta,
});

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

Рассматривая RESTFul API, типы и создатели действий для CRUD будут выглядеть следующим образом:

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

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

URL-адрес должен быть определенным образом динамическим, чтобы мы могли передавать ему параметры, когда это необходимо, например, в виде id.

Учитывая вышеизложенное и вдохновленный redux -auce, у меня возникла следующая идея: что, если бы мы могли определить имя наших действий, сообщить их URL-адреса и методы, а функция дает нам типы и создателей действий?

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

В приведенном выше примере у нас есть конфигурация для запроса, который создает элемент списка дел. Мы определяем имя действия и соответствующие URL и метод для запроса. На изображении ниже мы видим пример того, что нам возвращает createApiAction:

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

Функция createApiActions возвращает нам, создателям и типы настроенных запросов. Создатель принимает в качестве параметра объект, который может иметь:

  • полезная нагрузка, которая станет полезной нагрузкой для запроса и действия.
  • Любой другой дополнительный параметр, который станет частью мета действия и также будет использоваться для анализа динамических URL-адресов.

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

Затем будет отправлено следующее действие:

Динамические URL

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

Чтобы использовать их, нам нужно только добавить : и имя параметра в определение URL-адреса, как в примере ниже:

{ url: path/to/to-do/:id, method: 'anymethod' }

Для анализа параметров, определенных в url, будут использоваться значения, переданные создателю действия, как показано в следующем примере:

Выше мы настроили запросы: read, update e otherDynamicUrl. После определения мы используем их, передавая им объект, который содержит необходимые значения для анализа URL-адреса. Ниже у нас есть подробное объяснение прочитанного действия:

Как мы можем заметить на изображении выше, Arc использует параметр id, предоставленный создателю, для анализа url. Он также принимает любые дополнительные параметры, кроме полезной нагрузки, и вставляет их в мета действия.

По этой причине можно увидеть, что помимо url и метода, у нас также есть id внутри meta .

На изображении ниже мы можем увидеть объяснение действия обновления:

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

Основываясь на конфигурации Arc, наш CRUD теперь будет выглядеть так:

Если мы сравним этот код с предыдущим примером CRUD, мы можем заметить, что мы сохраняем 55 строк, и у нас есть код значительно более простой.

Виды и лечение ошибок

Типы, возвращаемые из createApiAction, можно использовать внутри редукторов, как показано в следующем примере:

Выше мы охватили все этапы запросов, а также обрабатываем ошибки.

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

Когда будет отправлено действие, относящееся к концу запроса, результат запроса будет помещен в action.payload в случае успеха.

При возникновении ошибки action.error будет истинным, а action.payload будет самой ошибкой запроса.

Установка

Интегрировать Arc в проект Redux довольно просто:

Во-первых, вам нужно добавить его в зависимости вашего проекта, выполнив:

yarn add redux-arc

or

npm install --save redux-arc

Затем вам необходимо настроить асинхронное промежуточное ПО:

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

asyncTask

Для большей гибкости Arc ожидает, что вы предоставите функцию, которая будет отвечать за выполнение запроса. В приведенном выше примере мы используем axios, но вы можете использовать любую другую библиотеку. Объект options будет содержать url, метод, а также полезную нагрузку для запроса. После завершения запроса вам нужно только выполнить функцию done, передав соответствующую ошибку и ответ.

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

Политики

Политики - это промежуточное ПО, которое вы регистрируете в Arc. После регистрации политику можно передать в конфигурации запроса, как показано в примере ниже:

Сначала мы объявили политику с именем anyPolicy. В каждой политике должна соблюдаться следующая подпись:

function PolicyName(store) {
  done => (action, error, response) => {
    done(action, error, response)
  };
}
PolicyName.applyPoint = 'beforeRequest' or 'onResponse';

В строке 8 мы определяем applyPoint политики, который является обязательным. Они указывают, в какой точке запроса будет выполняться политика. Доступные applyPoints: beforeRequest и onResponse.

В строке 9 мы зарегистрировали политику в Arc, используя метод register. После этого политика уже доступна для использования в любом запросе.

Чтобы использовать политики, как мы это делаем в строке 15, вам нужно только объявить параметр policy в конфигурации запроса и передать массив с политиками, которые вы хотите применить. к нему.

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

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

Мы проверяем, содержит ли полезная нагрузка действия идентификатор, и если да, то рассматриваем его как действие обновления. В этом случае мы добавляем id к url, меняем метод на put и удаляем id из полезной нагрузки. Наконец, мы передаем новое действие вперед.

Обратите внимание, что мы использовали applyPoint, это beforeRequest, потому что нам нужно изменить содержимое действия до начала запроса.

Политика createOrUpdate будет использоваться в качестве следующего примера:

Сначала мы определяем наш запрос с именем save и определяем url без id. Мы определяем метод как post, который будет использоваться при создании, и включаем политику createOrUpdate в запрос.

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

Бывают случаи, когда нужно изменить ответ вместо запроса. В таких случаях вам нужно только изменить applyPoint на 'onResponse'. Внутри политики клонируйте ответ, измените его данные и передайте их вперед, используя функцию done.

Заключение

Как мы видели в этом посте, Arc значительно сокращает шаблон, когда мы говорим о вызовах API, в то же время он дает нам, разработчикам, гибкость.

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

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

Вам понравился этот пост и вы считаете его полезным? Хлопайте 👏 ниже и помогите нам распространить информацию :)