[Перевод] Пишем API для React компонентов, часть 5: просто используйте композицию
Пишем API для React компонентов, часть 1: не создавайте конфликтующие пропсыПишем API для React компонентов, часть 2: давайте названия поведению, а не способам взаимодействия
Пишем API для React компонентов, часть 3: порядок пропсов важен
Пишем API для React компонентов, часть 4: опасайтесь Апропакалипсиса!
Пишем API для React компонентов, часть 5: просто используйте композицию
У нас есть компонент значка:
Вы видели их в различных приложениях, они показывают количество объектов в виде числа.
В cosmos Badge
(значок) имеет несколько цветов для каждого конкретного контекста (информация, опасность и т.д.)
У этого пользовательского интерфейса есть еще один похожий компонент — Label
.
У него то же есть несколько цветов для каждого контекста:
Посмотрите на эти два компонента и скажите одну хорошую и одну плохую вещь об их API (об их пропсах)
Что хорошо
У обоих компонентов есть одинаковый проп для внешнего вида: appearance
, это здорово. Мало того, у них одинаковые варианты для этого пропа! Если вы знаете как использовать appearance
в Badge
, то вы уже знаете как использовать appearance
в Label
Стремитесь к последовательным пропсам между компонентамиСовет № 2 из Пишем API для React компонентов, часть 2: давайте названия поведению, а не способам взаимодействия
Что плохо
То, как они принимают свои значения, отличается. У них обоих свой вариант.
Подсчет — count
, имеет смысл в рамках компонента Badge
, но с учетом всех остальных ваших компонентов это дополнительный API о котором придется помнить вашей команде и пользователям (разработчикам).
Давайте улучшим этот API
Чтобы бы быть последовательным, я назову этот проп content
, это наиболее общее название которое я смог придумать, — более общее чем просто label, text или value.
Мы потеряли некоторую детализацию, но получили большую последовательность. Мы все еще можем установить тип значения с помощью prop-types, так что думаю, что это хороший компромисс.
Но подождите, в React-е уже есть многоцелевой content
проп, он называется children
— дочерний.
Не переизобретайтеprops.children.
Если вы определили пропсы, которые принимают произвольные данные, не основанные на структуре данных, вероятно, лучше использовать composition (композицию) — Brent Jackson
Вот совет этой статьи — При выборе между композицией и пропсами, выбирайте композицию.
Давайте проведем рефакторинг этого API при помощи children
— дочерних элементов:
12
Выглядит отлично.
Бонус: когда вы вместо пропа text
используете children
, разработчик использующий этот компонент получает больше гибкости без необходимости изменять компонент.
К примеру, вот сообщение предупреждение
, в нем, я хочу добавить иконку перед текстом.
Используя children
я могу добавить иконку в это сообщение предупреждение
, не возвращаясь к этому компоненту и не меняя его.
// Плохо - приходиться добавлять поддержку иконок
// Хорошо
This is an important message!
По совпадению, когда я писал этот текст, я увидел твит Брэда Фроста:
Эй, React друзья, нужна небольшая помощь. Я продолжаю сталкиваться с этим шаблоном, где определенные компоненты (особенно списки) могут быть разделены на более мелкие компоненты или управляться путем передачи объекта. Какой из вариантов лучше?
Выглядит знакомо?
Прежде всего, давайте не будем использовать проп text
и вместо этого будем использовать children
.
// вместо этого:
// напишем это:
Home
Теперь, когда мы разобрались с этим, давайте поговорим об этих двух вариантах API.
Как не сложно догадаться, мне нравится первый.
- Вам не нужно думать о том как называется проп —
text
?label
? Это простоchildren
. - Вы можете добавить свое
className
илиtarget
к нему, если нужно. Для второго варианта вам нужно убедиться, что он поддерживает эти свойства или просто передает их базовому элементу. - Это позволяет обернуть дочерний элемент в контекст или в компонент более высокого уровня.
Исключение из правила:
Что, если Брэд хочет запретить разработчику выполнять какие-либо настройки, о которых я упоминал выше? Тогда давать разработчику больше гибкости, в его случае, будет ошибкой!
Вот мой ответ Брэду.
Больше примеров
Вот еще несколько примеров того, как этот совет может улучшить ваш код, последний мой любимый.
Формы — отличный пример использования, мы хотим управлять макетом формы, отображать ошибки и т.д. Но в то же время мы не хотим лишаться возможностей для расширения.
// #1 Плохо
// к чему относится этот id,
// к label или к input?
// #2 Хорошо
// #3 то же хорошо
Последний пример особенно интересный.
Иногда вам нужен компонент который будет использоваться в очень разных ситуациях. Не просто сделать компонент который будет гибок, и при этом все еще иметь простой API.
Вот где на помощь приходит инверсия управления — пусть пользователь компонента сам решает что рендерить. В мире React-а этот паттерн называется render prop pattern (паттерн рендер-пропсов).
Компонент с рендер-пропом берёт функцию, которая возвращает React-элемент, и вызывает её вместо реализации собственного рендера.из документации React Рендер-пропсы
Одним из наиболее популярных примеров рендер-пропсов является официальный Context API.
В следующем примере компонент App
контролирует данные, но не контролирует их рендеринг, он передает этот контроль компоненту Counter
(счетчик).
// создаем новый контекст
const MyContext = React.createContext()
// значение передается вниз
// через контекст провайдер
function App() {
return (
)
}
// и потребляется через
// потребителя контекста
function Counter() {
return (
{value => (
the count is: {value}
)}
)
}
Заметили что-нибудь интересное в этом Consumer
API?
Вместо того чтобы создавать новый API, он использует children
, чтобы принять функцию которая скажет ему как рендерить!
// Плохо
(
the count is: {value}
)} />
// Хорошо
{value => (
the count is: {value}
)}
Вернитесь к своему коду и найдите компонент который принимает какие либо пропсы, когда он, при этом, может легко использовать children
.