Flux на пальцах

Основные принципы Flux

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

  2. В приложении существует один объект, который хранит состояние всего приложения. Я буду называть его "глобальным состоянием", хотя не совсем уверен, что это наиболее удачный термин. Некоторые компоненты по желанию разработчика "подписываются" на интересующую их часть глобального состояния. С течением времени глобальное состояние может изменяться, а все подписанные на него компоненты получают обновления автоматически.

  3. Запрещено явно изменять глобальное состояние внутри компонента. Для изменения глобального состояния компоненты вызывают специальную функцию dispatch. Отслеживание хода выполнения этой функции является anti-pattern, так как нарушает первый принцип Flux. На практике глобальное состояние будет содержать всю необходимую вашему компоненту информацию, например, статус выполнения API запроса и ошибки. Эта и другая информация будет своевременно и явно передаваться вашему компоненту с помощью props.

Важное примечание: глобальное состояние описывает лишь состояние вашего front-end приложения в отдельной вкладке и хранится исключительно в оперативной памяти браузера. Таким образом, оно будет утеряно, если пользователь нажмет F5, что абсолютно нормально, ожидаемо и by design.

Практический пример

Допустим, у нас есть сайт интернет-магазина: в центре страницы мы увидим список товаров, в панели навигации — иконку корзины с количеством товаров и общей их стоимостью, а где-то справа — блок с детализацией товаров, добавленных в корзину. Одним словом, достаточно распространенный сценарий.

Сценарий с точки зрения пользователя

  1. Пользователь нажимает на кнопку "Добавить в корзину".

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

  3. Выполняется запрос серверного API.

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

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

Если бы мы писали этот сценарий на jQuery, то пришлось бы написать много кода для работы с DOM'ом. В процессе реализации все новых требований заказчика код становился бы все запутаннее, и, с большой долей вероятности, что-нибудь в итоге сломалось бы, а сложность и стоимость поддержки постоянно увеличивалась бы с течением времени и новых "хотелок".

Этот же сценарий с точки зрения Flux

Примечание: компоненты "Добавить в корзину", "Уведомления", "Корзина" и "Детализация Корзины" подписаны на глобальное состояние.

  1. Пользователь нажимает на кнопку "Добавить в корзину", что приводит к вызову функции dispatch.

  2. Эта функция обновляет глобальное состояние: кнопка "Добавить в корзину" получает новое значение prop loading равное true, что делает ее выключенной, а ее иконка меняется на индикатор загрузки согласно исходному коду этого компонента.

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

  4. В случае успеха обновим глобальное состояние: компонент "Уведомления" получит новое значение prop message, что сделает его видимым для пользователя, компонент "Корзина" получит значения prop count с новым количеством товаров и prop value с суммой заказа, компонент "Детализация корзины" получит значение prop items — обновленный список объектов, соответствующих всем товарам, добавленным в корзину. Если в будущем заказчик пожелает, чтобы на странице происходило что-то еще, мы легко сможем воплотить это в жизнь, не меняя ни код других компонентов, ни функцию, которая выполняет бизнес-логику. Нам достаточно лишь реализовать новый компонент и в нем же указать, какая часть глобального состояния нас интересует.

  5. Если API вернул ошибку, то компонент "Уведомления" получит соответствующее значение prop message и покажет пользователю информационное сообщение.

  6. Функция в последний раз обновит глобальное состояние, сообщив кнопке "Добавить в корзину" новое значение prop loading равное false. Кнопка снова вернется в свое первоначальное состояние.

Пример кода такой функции

function addItemToCart(itemId) {
  return (dispatch) => {
    dispatch(addItemToCartStarted(itemId));

    addItemToCartAPICall(itemId)
      .then(
        (data) => {
          dispatch(itemToCardAdded(data));
          dispatch(addItemToCartFinished(data));
        }
      )
      .catch(error => dispatch(addItemToCartFailed(itemId, error)));
  }
}

Last updated