Отделите логику от пользовательского интерфейса, создав свои собственные перехватчики реакции

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

Хуки обычно используются для извлечения общей логики, которая используется в нескольких компонентах. Частыми случаями использования являются запросы API и отправка форм. Подход, за который я выступаю в этой статье, заключается в том, чтобы использовать хуки не только для повторно используемой логики, но и для каждой логики в вашем приложении. Таким образом, ваш код станет значительно более чистым и управляемым, поскольку ваши компоненты React будут содержать только код рендеринга, в то время как ваши файлы ловушек будут иметь всю логику, связанную с этим компонентом. Следовательно, ваш пользовательский интерфейс и логика становятся разделенными, и вы можете повторно использовать только пользовательский интерфейс, логику или и то, и другое в других частях вашего приложения.

Чтобы проиллюстрировать это, мы собираемся использовать в качестве примера компонент представления списка в приложении React. Компонент покажет список карточек, каждая карточка имеет одного пользователя, полученный из бесплатного API ReqRes, который поддерживает разбиение на страницы. Мы реализуем разбиение на страницы в этом компоненте, чтобы пользователь мог щелкнуть кнопку «Загрузить еще», чтобы загрузить больше пользователей, не обновляя страницу.

Учебник по созданию собственных крючков

В этом разделе мы кратко рассмотрим создание ваших собственных хуков. Я предполагаю, что вы знакомы с основами хуков, а также с общими хуками, такими как useState и useEffect. Создание и использование собственных хуков несложно, если вы помните два золотых правила хуков React:

  1. Хуки могут вызываться только в других хуках или функциональных компонентах.
  2. Хуки должны вызываться на верхнем уровне (не внутри условий или петель).

При написании пользовательских хуков я считаю, что лучше всего рассматривать хуки как просто функции, которые могут сохранять состояние и работать с этим состоянием. Функция (ловушка) обычно возвращает вызывающей стороне состояние, которое она отслеживает, а также некоторые методы, которые работают с этим состоянием. Поскольку хуки могут использовать другие хуки (правило 1), мы можем использовать useState для хранения состояния нашей пользовательской ловушки.

Рассмотрим наш сценарий, в котором нам нужно написать ловушку для разбивки на страницы. Какое состояние сохранит наш крючок? Наиболее очевидный из них - сколько элементов уже загружено и что нужно загрузить дальше. Следовательно, мы будем хранить текущий номер страницы в состоянии. Еще нам нужно сохранить данные, которые загружаются из API. Наконец, мы будем отслеживать, нужно ли загружать дополнительные элементы.

Что плохого в традиционном подходе?

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

Хотя эта структура отлично справится со своей задачей, у нее есть некоторые недостатки:

  • Код рендеринга пользовательского интерфейса и логика javascript для получения сообщений будут смешаны в одном файле, что затруднит понимание и рассуждение о коде.
  • Это нарушает принцип единой ответственности (SRP) разработки программного обеспечения, в котором одна единица кода должна отвечать за одну структурированную задачу (либо отображение пользовательского интерфейса, либо обработка логики разбиения на страницы).
  • Если такая же логика разбиения на страницы позже потребуется в других компонентах приложения, нам придется писать один и тот же код несколько раз.
  • Это сделает наши файлы кода длиннее и утомительнее для навигации.

Здесь вы можете задать очень уместный вопрос:

Почему бы просто не переместить функции-обработчики в отдельный файл вместо создания хуков?

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

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

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

Подход крючков

Теперь взгляните на эту вторую диаграмму, на которой мы извлекаем логику разбиения на страницы в ловушку:

Здесь сразу видно, что четыре проблемы, которые мы обозначили выше, решены.

Получение рук

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

  1. Инициализируйте приложение с помощью CRA и настройте структуру папок.
  2. Настроить App.js

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

4. Создайте компонент «Список пользователей» для отображения списка пользователей.

Здесь вы заметите, что мы использовали хук usePaginationFromUrl (который мы вскоре создадим). Напомним, что извлечение логики разбиения на страницы в ловушку означает, что нашему компоненту UserList не нужно беспокоиться о каких-либо деталях реализации. Следовательно, мы можем просто использовать хук разбиения на страницы, как если бы он уже реализован, и импортировать из него соответствующие переменные и методы. Это послужит руководством к тому, когда мы действительно доберемся до реализации ловушки.

5. Создайте перехватчик нумерации страниц.

Наш хук нумерации страниц хранит 3 важных элемента состояния:

pageNumber: последний номер страницы, загруженной обработчиком. Пользователь ловушки может увеличить это значение с помощью метода incrementPage, который затем удобно запускает загрузку данных следующей страницы, поскольку pageNumber является зависимостью от useEffect, внутри которой загружается следующая страница.

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

данные: фактические данные, загруженные обработчиком. Это массив, содержащий всех пользователей текущей и всех предыдущих страниц, загруженных на данный момент.

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

Пагинация в действии

Вот и все! Пора увидеть конечный результат.

Заключение

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