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

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

В начале своего пути с React я использовал его так же, как и весь остальной мир JavaScript (jQuery). JavaScript был просто хорошим расширением уже работающего пользовательского интерфейса. Весь веб-сайт был создан на стороне сервера, и только небольшая часть веб-сайта была динамической на стороне пользователя. Данные для части JavaScript вводились в отображаемый HTML-код на стороне сервера и использовались JavaScript на стороне пользователя. Я использовал этот подход для небольших частей фронтенда, написанных на React. И это сработало хорошо! Зависимости для данной страницы были там, когда это было необходимо, вводились сервером и потреблялись React.

Одностраничные приложения

Затем я начал работать в Ackee, и каким-то образом все приложения, над которыми я работал, оказались SPA. Я не хочу углубляться в СПА и что это такое, когда и зачем их использовать, но для тех, кто не знает, что такое СПА; Одностраничное приложение — это приложение, написанное на JavaScript, работающее в браузере и динамически манипулирующее HTML-файлом вместо запроса нового HTML-файла с сервера.

Одной из больших проблем, сопутствующих SPA (а их много), было управление зависимостями данных. Внедрение данных для JavaScript в HTML на стороне сервера устарело. Нет смысла вводить все данные для всего приложения во время рендеринга сервера. Итак, что же делать?

К счастью для меня, в то время появилось много новых классных библиотек. React Router для рендеринга контента SPA на основе URL-адреса был отправной точкой. В сочетании с базовыми вызовами AJAX мы смогли получить данные для текущего контента, отображаемого React (каждый компонент запрашивает нужные ему данные). Мы могли запрашивать данные по запросу, когда они были нужны, но где и как их хранить? В компоненте, который запросил для них? Или в глобальной переменной? Какое-то хранилище? Где?

Флюс

Опять же, к счастью для меня, в то время Facebook изобрел новую архитектуру приложений под названием Flux. Flux — это не только хранение данных приложения, но и весь поток данных. Поток данных в Flux является однонаправленным, и данные могут быть изменены только с помощью так называемых действий (без общедоступных сеттеров или геттеров). Действие определяет манипулирование данными. Каждое действие ставится в очередь, а затем применяется к хранилищу. В вашем приложении может быть больше хранилищ, каждое из которых содержит часть зависимостей данных. После изменения содержимого магазина магазин генерирует событие «change» для зарегистрированных пользовательских интерфейсов. Затем пользовательский интерфейс может обновляться на основе новых данных. Вы можете увидеть поток на диаграмме ниже. Данные хранятся в глобальных хранилищах и обновляются только с помощью отправленных действий. Пользовательский интерфейс может наблюдать за хранилищами для рендеринга/повторного рендеринга, если данные, от которых он зависит, изменились. Круто, не правда ли? Нам нужно что-то еще?

Редукс

После появления Flux произошел настоящий бум. Многие библиотеки пытались внедрить Flux, и мы были вынуждены (вынуждены) выбрать одну. Поэтому мы выбрали Marty.js. Должен признать, отличный, но через некоторое время мы поняли, что у него есть некоторые недостатки. Первый из минусов: Flux подчеркивает использование более чем одного хранилища. Это отстой! Это может добавить так много стандартного кода в ваше приложение. Следующий: если честно, Marty.js пытается быть больше фреймворком, чем библиотекой, и заставляет вас использовать свою инфраструктуру для получения зависимостей данных и других вещей, и нам пришлось взломать его несколько раз. А потом появился Redux (благослови Дэна Абрамова). Что такое Редукс?

Я процитирую эту крутую статью: Redux = Flux + Функциональное программирование. Сам по себе Flux — это всего лишь предложение архитектуры. Redux — это библиотека, реализация Flux. Но Redux нарушает одно правило: у него только один магазин! Итак, еще раз, Redux — это реализация Flux, использующая только одно хранилище. Где функциональное программирование? Как я уже говорил, сам Flux ничего не говорит о реализации. Итак, как применить действия к магазину? Используйте функциональное программирование!

const initialState = { counter: 0 };
const previousState = ...;
const reducer = (state = initialState, action) => {
  switch(action.type) {
    case 'INC':
      return {
        ...state,
        counter: state.counter + 1;
      };
    default:
      return state;
  }
}
const newState = [action1, action2, action3].reduce(reducer, previousState);

Как вы можете видеть выше, у нас есть previousState, функция редуктора, очередь действий, и с помощью метода редуктора мы можем сгенерировать новое состояние нашего приложения. Это все! Redux довольно легкий. Он дает вам оболочку хранилища, чтобы вы могли отправлять действия в хранилище, и дает вам возможность наблюдать за изменениями в хранилище. Вы даете ему функцию редуктора и initialState. Остальная работа на вас. Никакого шаблонного кода, никакой принудительной (своего рода) инфраструктуры….. подождите! Где выполнять вызовы API? Как насчет бизнес-логики?

Redux-сага и исключение бизнес-логики

Marty.js поступил умно. Он извлек бизнес-логику за пределы компонентов React в свою инфраструктуру. Разделение ответственности на практике, круто! Но приняв Redux, мы вернулись к извлечению данных в компонентах React, что означает смешение бизнес-логики с рендерингом данных. Redux сам по себе слишком прост. Не круто, не круто.

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

Мы начали с промежуточного ПО redux-thunk. Это позволяет нам отправлять функцию как действие. Сигнатура функционального действия — (dispatch: Function, getState: Function) => null. Как мы видим, redux-thunk дает нам полный доступ к хранилищу и позволяет писать бизнес-логику в действиях. Лучше, чем ничего, но совсем не круто.

Затем мы открыли для себя redux-saga, и это полностью изменило то, как мы сейчас пишем приложения Redux. Redux-saga — это промежуточное программное обеспечение, но вы можете думать о нем как о слое бизнес-логики приложения Redux. Он использует асинхронные генераторы для создания слоя. При создании промежуточного программного обеспечения вы даете ему запускаемый корневой генератор, и промежуточное программное обеспечение вызывает его. В корневом генераторе вы можете вызывать другие генераторы, ждать действий, создавать новые действия, выбирать данные из хранилища, выполнять вызовы API и т. д. (это безумно мощно, ознакомьтесь с документацией). Например, он позволяет вам обрабатывать сложную логику, такую ​​​​как обновление токенов доступа, когда API возвращает 401. Благодаря Redux-saga вы можете отправлять только простые действия в свои компоненты React, а затем обрабатывать остальные в промежуточном программном обеспечении.

Вывод

Я рассказал вам больше половины своего опыта работы с React и его экосистемой. Что отсюда взять? React — это библиотека для пользовательских интерфейсов. Не используйте его для хранения данных, даже не используйте его для извлечения данных. Используйте его только для запроса данных. Flux позволяет избежать побочных эффектов в вашем приложении благодаря однонаправленному потоку данных. Данные хранятся в хранилищах и управляются только действиями. Redux использует только одно хранилище, один источник правды для хранения данных. Он использует функциональное программирование (функции редуктора) для управления данными. Он упрощает Flux, делает приложения более предсказуемыми, его легко тестировать и даже позволяет легко создавать путешествия во времени! А Redux-saga дает вам невероятно мощный слой для вашей бизнес-логики и управления зависимостями данных!

Хотите узнать, как мы используем Redux-saga для обработки зависимостей данных? Я объясню это в следующей статье.