Идеальный библиотечный компонент React:
- Предоставляет простой и понятный API;
- Имеет несколько модификаций и вариантов использования, легко настраивается при необходимости;
- Позволяет расширять и тонко контролировать свое поведение (для сложных ситуаций).
В поисках этого Идеального компонента сообщество React разработало несколько классных паттернов, которые вы должны взять на вооружение. Все они так или иначе позволяют разработчику вмешиваться в работу компонента и настраивать или модифицировать его под свои нужды.
Для удобства мы разберем все паттерны на одном примере и по одному плану. В качестве компонента будет выступать простой счетчик:
Для каждого шаблона будет небольшое введение, реальный вариант использования (со ссылкой на GitHub, где вы найдете и примеры реализации) и разбор плюсов и минусов. Затем подведем небольшой итог и выставим оценки по двум критериям:
- Инверсия контроля (управления) – уровень гибкости и контроля, который ваш компонент предоставляет пользователям (другим разработчикам).
- Сложность реализации и использования – для вас и других пользователей.
Весь исходный код доступен на GitHub: https://github.com/alex83130/advanced-react-patterns.
Также посмотрим, какие публичные библиотеки React уже используют тот или иной паттерн.
Паттерн #1. Составные компоненты
Пример использования
Github: https://github.com/alex83130/advanced-react-patterns/tree/main/src/patterns/compound-component
Плюсы
Уменьшается сложность API
Больше нет необходимости передавать все параметры в один гигантский родительский компонент и затем пробрасывать их до дочерних элементов интерфейса. Теперь каждое свойство сразу прикрепляется к своему подкомпоненту – это выглядит проще и логичнее.
Гибкая структура разметки
Так как все элементы пользовательского интерфейса вынесены в отдельные подкомпоненты, разработчик может их перегруппировать или даже убрать по своему усмотрению. Таким образом реализуется модифицируемость вашего компонента.
Разделение ответственности
Основная логика содержится в базовом компоненте счетчика, а затем используется React.Context для совместного использования состояния и обработки событий в дочерних элементах. В итоге мы получаем четкое разделение ответственности внутри компонента.
Минусы
Слишком большая гибкость пользовательского интерфейса
Гибкость – это не всегда хорошо. Без должного контроля, она может привести к изменению интерфейса или даже поломке компонента. Например, ничто не мешает пользователю добавить дополнительный элемент или, наоборот, забыть что-то важное (подкомпонент или параметр).
Уровень гибкости, который вы готовы предоставить пользователю, зависит от многих аспектов. Иногда большая свобода не требуется.
Громоздкая разметка
Очевидно, что количество строк разметки существенно увеличивается, ведь каждый элемент представлен отдельным компонентом, а не спрятан внутри родителя. Особенно это чувствуется при использовании линтеров (ESLint) или форматировщиков кода (Prettier).
В масштабе одного компонента это не кажется большой проблемой, но если вы посмотрите на общую картину, то, возможно, передумаете.
Оценки
- Инверсия управления: 1 из 4
- Сложность реализации: 1 из 4
Публичные библиотеки, использующие этот паттерн
Паттерн #2. Управление свойствами
Пример использования
GitHub: https://github.com/alex83130/advanced-react-patterns/tree/main/src/patterns/control-props
Плюсы
Больше контроля
Пользователь полностью контролирует состояние компонента и напрямую влияет на его поведение.
Минусы
Сложность реализации
Управляемый компонент нельзя просто подключить в одном месте и забыть. Требуется написать больше кода (JSX-разметка, useState и обработчик handleChange)
Оценки
- Инверсия управления: 2 из 4
- Сложность реализации: 1 из 4
Публичные библиотеки, использующие этот шаблон
Паттерн #3. Кастомные хуки
Мы можем пойти еще дальше в инверсии управления и перенести основную логику компонента в кастомный хук, который доступен пользователю и предоставляет несколько внутренних логик (состояния, обработчики). Таким образом, пользователь может лучше контролировать ваш компонент.
Пример использования
GitHub: https://github.com/alex83130/advanced-react-patterns/tree/main/src/patterns/custom-hooks
Плюсы
Больше контроля
Пользователь может добавить свою собственную логику между вашим хуком и элементом JSX, что позволяет изменить дефолтное поведение компонента.
Минусы
Сложность реализации
Логическая часть компонента полностью отделена от рендеринга, а значит пользователю придется связать их самостоятельно. Чтобы реализовать все правильно, разработчик должен хорошо понимать, как работает система в целом.
Оценки
- Инверсия управления: 2 из 4
- Сложность реализации: 2 из 4
Публичные библиотеки, использующие этот шаблон
Паттерн #4. Геттер пропсов
Замаскировать эту сложность пытается шаблон Props Getter. Компонент предоставляет геттеры, которые возвращают список пропсов для связи с определенным элементом JSX-разметки. Теперь пользователь не должен указывать атрибуты самостоятельно, достаточно просто передать полученный список.
При этом остается возможность переопределить необходимые свойства.
Пример использования
GitHub: https://github.com/alex83130/advanced-react-patterns/tree/main/src/patterns/props-getters
Плюсы
Простота использования
Интеграция компонента в код становится проще, пользователю нужно только подключить правильный геттер к правильному элементу JSX. Дополнительная сложность от него скрыта.
Гибкость
Пользователь компонента по-прежнему имеет возможность перегружать пропсы при необходимости.
Минусы
Непрозрачность
Геттеры привносят дополнительный уровень абстракции, что облегчает интеграцию компонента, но одновременно и делает его менее прозрачным, "магическим". При этом чтобы правильно переопределить какое-либо свойство, очень важно понимать их внутреннюю логику и ничего не сломать.
Оценки
- Инверсия управления: 3 из 4
- Сложность интеграции: 3 из 4
Публичные библиотеки, использующие этот шаблон
Паттерн #5. Редуктор состояния
Самый продвинутый с точки зрения инверсии контроля паттерн, который дает пользователю расширенные возможности изменить внутреннюю работу вашего компонента.
Пример использования
GitHub: https://github.com/alex83130/advanced-react-patterns/tree/main/src/patterns/state-reducer
В этом примере объединены паттерны редуктора состояния и кастомного хука. Но ничего не мешает вам использовать редуктор с составными компонентами, передав его главному компоненту Counter.
Плюсы
Больше контроля
Использование редукторов состояний – лучший способ передать управление компонентом пользователю. Оно идеально подходит для сложных случаев, когда требуется самая тонкая настройка. Все действия компонента доступны извне и могут быть переопределены.
Минусы
Сложность реализации
Этот паттерн, безусловно, является самым сложным для реализации, как для вас, так и для пользователя.
Непрозрачность
Так как любое действие редуктора может быть изменено, от пользователя требуется хорошее понимание внутренней логики компонента.
Оценки
- Инверсия управления: 4 из 4
- Сложность интеграции: 4 из 4
Публичные библиотеки, использующие этот шаблон
Нельзя забывать о том, что "с большой силой приходит большая ответственность, Питер". Чем больший контроль вы передаете пользователю, тем сложнее работать с вашим компонентом – его уже не получится просто подключить и сразу же начать пользоваться. Вы, как разработчик, должны самостоятельно определить, какой шаблон отвечает вашим задачам больше всего.
Вам в помощь небольшая диаграмма, классифицирующая все рассмотренные паттерны по сложности интеграции и инверсии управления.
А какие паттерны используете вы?