Разбираемся с Render Props и HOC в React

Детальное представление Render Props и компонентов высшего порядка в React

Ann Caly
NOP::Nuances of Programming
6 min readDec 10, 2018

--

reactjs.org

Для чего нужны эти паттерны?

React предлагает Компоненты, представляющие собой простой способ повторного использования кода. Компонент инкапсулирует множество вещей от контента, стилей и до бизнес-логики. Поэтому в идеале компонент может содержать комбинацию из html, css и js, каждый из которых имеет одну цель — единственную ответственность.

Совет: при помощи Bit (GitHub) вы можете организовать коллекцию переиспользуемых компонентов и применять их совместно с другими разработчиками. Компоненты могут быть разработаны и импортированы из разных проектов и приложений. Это гораздо быстрее, чем переписывать их или же поддерживать большую библиотеку. Просто попробуйте :)

Пример

Предположим, что мы работаем над приложением для E-commerce. Как и в любом другом E-commerce приложении пользователь видит список всех доступных товаров, а также может добавить любой из них в корзину. Мы же получаем данные о товарах через API и показываем каталог товаров в виде карточек.

В этом случае компонент React можно реализовать подобным образом:

Link to Code

Для админов существует панель администратора, где они могут добавлять или удалять товары. В этой панели мы запрашиваем данные о товарах с того же API и показываем каталог товаров в форме таблицы.

React компонент может быть таким:

Link to Code

Единственное, что сразу же бросается в глаза - это то, что оба компонента реализуют логику получения данных о товарах.

В дальнейшем такие ситуации могут возникнуть снова.

  • Нам стоит использовать данные о товарах и показывать их другим способом.
  • Запрашивать данные о товарах из разных API (например, для страницы корзины пользователя), но показывать их так, как мы это делали в компоненте ProductList.
  • Вместо получения данных от API, мы должны обращаться к ним через localStorage.
  • В табличном каталоге товаров кнопку удаления заменить на кнопку, выполняющую другое действие.

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

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

Давайте отрефакторим наш первый компонент. Он будет получать данные товаров в качестве свойства (prop) и рендерить (метод render) каталог товаров в виде списка карточек, как мы делали до этого. Так как нам больше не нужно состояние компонента и методы жизненного цикла, мы переделаем компонент в функциональный.

Теперь он будет выглядеть так:

ProductList.js (Link to Code)

Подобно ProductList, компонент ProductTableбудет функциональным компонентом, который получает данные о продукте в виде свойства и рендерит их как строки таблицы.

Далее создадим компонент под названием ProductsData, который запрашивает данные о товарах через API. Получение данных и обновление состояния будет происходить как в компоненте ProductList. Но что нужно поместить в метод render этого компонента?

ProductData.js (Link to Code)

Если мы просто передадим туда компонент ProductList, то мы не сможем переиспользовать этот компонент в ProductTable. Если данный компонент каким-то образом сможет узнать, что ему рендерить, то проблема будет решена. В одном месте мы укажем ему отрендерить ProductList, а в административной панели — ProductTable.

Здесь в игру вступают render props и HOC (компоненты высшего порядка). Они являются всего лишь способами, при помощи которых компонент узнает, что ему нужно отрендерить. Это еще больше увеличивает возможность повторного использования кода.

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

Render Props

На концептуальном уровне понять Render Props очень просто. Давайте забудем на минуту о React и посмотрим на вещи в контексте ванильного JavaScript.

У нас есть функция, которая вычисляет сумму двух чисел. Для начала мы просто хотим вывести результат в консоль. Итак, мы написали следующую функцию:

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

Это можно сделать следующим образом:

Link to Code

Мы передаем функции sum в качестве аргумента fn функцию обратного вызова. Затем функция sum вычисляет результат и вызывает функцию fn с этим результатом в качестве аргумента. Таким способом функция обратного вызова получает результат и может делать с ним всё, что угодно.

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

Вместо функции, вычисляющей сумму двух чисел у нас есть компонент ProductsData, который получает данные товаров. Теперь же компоненту ProductsData можно передать функцию через свойства (props). Компонент ProductsData получит данные товаров и предоставит их функции, которая была передана в качестве свойства. После чего переданная функция сможет делать с данными товаров всё, что захочет .

В React это можно реализовать следующим образом:

Link to Code

Подобно аргументу fn у нас есть render prop, которому будет передана функция. Затем компонент ProductData вызывает эту функцию с данными товаров в качестве аргумента.

Следовательно, мы можем использовать компонент ProductData как показано ниже.

Link to Code

Как мы видим, render props — достаточно универсальный паттерн. Большинство задач могут быть выполнены довольно однообразно. И именно поэтому мы можем “выстрелить себе в ногу”.

Простой способ избежать вложенности — это разбить компонент на более мелкие и переместить их в отдельные файлы. Ещё один способ — это создать больше компонентов и собирать их в один, вместо длинных функций внутри render props.

Далее мы рассмотрим другой популярный паттерн под названием HOC.

Компоненты высшего порядка (HOC)

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

Если описание кажется вам знакомым, то это потому, что данный паттерн похож на паттерн декоратор, широко используемый в Mobx. Многие языки, такие как Python, имеют встроенные декораторы и скоро JavaScript также станет их поддерживать. HOC очень похожи на декораторы.

Понять HOC проще на примере кода, чем на словах. Поэтому давайте сначала взглянем на код.

Link to Code

Как мы видим, логика получения данных и обновления состояния похожа на то, как мы это делали в render props. Единственное изменение заключается в том, что класс компонента находится внутри функции. Функция принимает компонент в качестве аргумента, и затем внутри метода render класса мы рендерим переданный компонент, но с дополнительными свойствами (props). Довольно простая реализация паттерна с таким сложным названием, не так ли?

Link to Code

Итак, мы выяснили для чего нам нужны render props, HOC и как мы можем их реализовать.

Остается один вопрос: как выбрать между Render Props и HOC? На эту тему довольно много статей, так что сейчас я не буду об этом говорить.

Заключение

В данной статье мы узнали, для чего нам нужны паттерны Render Props и HOC, суть каждого из них, а также способы их использования для создания переиспользуемых компонентов.

Перевод статьи Aditya Agarwal: Understanding React Render Props and HOC

--

--