Подробное объяснение системы React для повторного использования логики компонентов.

Вы когда-нибудь работали с кодовой базой или просматривали учебное пособие в Интернете и сталкивались с чем-то похожим на это:
export default withResponsive(Home)
Что ж, тогда вы уже столкнулись с Компонентой более высокого порядка, осознали вы это или нет.
Компонент более высокого порядка (сокращенно HOC) - это способ React наделять несколько компонентов повторяющимися функциями или поведением без необходимости многократно использовать один и тот же код в компоненте. Это очень умный способ следовать девизу DRY (Dont Repeat Yourself) и сократить беспорядок в коде.
HOC - это функция, которая принимает компонент в качестве аргумента и возвращает новый улучшенный компонент.
const EnhancedComponent = higherOrderComponent(OriginalComponent)
В заголовке этой статьи я использовал пример Брюса Уэйна против Бэтмена. Основное различие между ними заключается в том, что Бэтмен - это улучшенная версия Брюса с необычным костюмом и множеством гаджетов, которые он использует для борьбы с преступностью. В этом примере Брюс является исходным компонентом, а Бэтмен - расширенным компонентом. Его костюм (а также гаджеты и Бэтмобиль) - HOC.
const Batman = withBatSuit(BruceWayne)
HOC withBatSuit даст Брюсу доступ к его объекту BatSuit и превратит его в Темного рыцаря Готэма.
Итак, как именно он работает и когда его целесообразно использовать?
Когда использовать HOC
Допустим, у вас есть проект, в котором у вас есть несколько разных счетчиков, подсчитывающих какое-то поведение. В одном компоненте у вас есть кнопка, которая подсчитывает количество нажатий на нее, затем в другом компоненте у вас есть счетчик, отслеживающий количество раз, когда вы наводите указатель мыши на определенный div, затем в еще одном вы подсчитываете количество раз определенная страница посещена. Все эти компоненты будут иметь аналогичное поведение (увеличение счетчика на 1 после определенного действия), но, поскольку все они немного отличаются, мы, к сожалению, не можем сделать все те же самые компоненты.
Теперь, до HOC, вам нужно было бы записать поведение incrementCounter в каждый из них, а затем иметь какую-то переменную состояния, которая отслеживает счетчик. Тем не менее, это требует частого копирования, вставки и повторения кода. Что, если бы мы могли дать каждому компоненту функцию incrementCounter, а также переменную state.count, а затем настраивать только действие, запускающее функцию incrementCounter в каждом из них?
Это именно то, что мы можем сделать!
Другой вариант использования - например, определение способа отображения страницы в зависимости от того, обращается ли к ней пользователь со своего ноутбука, телефона или планшета. Если ваш веб-сайт адаптируется к мобильным устройствам, существует довольно распространенный шаблон, когда вы передаете компоненту объект с именем isResponsive, который является логическим значением, указывающим истину или ложь. Вместо того, чтобы передавать это в качестве свойств каждому компоненту, которому требуется доступ, вы можете визуализировать компонент, используя HOC, называемый withResponsive, который предоставит каждому компоненту доступ к объекту isResponsive, определяющему, как он должен отображаться пользователю.
Это два распространенных использования, но вы можете использовать его буквально везде, где вам нужно несколько компонентов для общего поведения.
Реализация HOC в вашем приложении
Вернемся к нашему первому примеру добавления счетчика к нескольким компонентам вашего приложения.
Если бы мы жестко закодировали поведение счетчика в каждом приложении, вы бы получили что-то вроде этого:
App.js
function App() {
return (
<div className="App">
<ClickCounterWithoutHOC/>
<HoverCounterWithoutHOC/>
</div>
);
}
export default App;
ClickCounterWithoutHOC
import React, {useState} from 'react'
const ClickCounterWithoutHOC = () => {
const [count, setCount] = useState(0)
const incrementCount = () => setCount(prevCount => prevCount + 1)
return (
<button onClick ={incrementCount}>
Clicked {count} times
</button>
)
}
export default ClickCounterWithoutHOC
HoverCounterWithoutHOC
import React, {useState} from 'react'
const HoverCounterWithoutHOC = () => {
const [count, setCount] = useState(0)
const incrementCount = () => setCount(prevCount => prevCount + 1)
return (
<button onMouseOver ={incrementCount}>
Hovered {count} times
</button>
)
}
export default HoverCounterWithoutHOC
Как вы видете. Этот шаблон хорош, и он работает, но мы повторяем много кода между HoverCounter и ClickCounter. Теперь представьте сценарий, в котором есть 5 или даже 50 других компонентов, которым требуется такое поведение счетчика, и внезапно у вас есть очень неэффективная кодовая база.

Вместо этого давайте создадим компонент более высокого порядка с именем withCounter.js, который добавит нам поведение count и incrementCount.
withCounter
import React, {useState} from 'react'
const withCounter = OriginalComponent => {
const EnhancedComponent = () => {
const [count, setCount] = useState(0)
const incrementCount = () => setCount(prevCount => prevCount + 1)
return (
<OriginalComponent
incrementCount ={incrementCount}
count={count}
/>
)
}
return EnhancedComponent
}
export default withCounter
Вы увидите, что этот компонент перемещает объявления count и increment count в withCounter.js, а затем возвращает EnhancedComponent, который представляет собой просто OriginalComponent с incrementCount и count, переданный ему в качестве свойств.
Довольно простая концепция, если вы увидите ее на практике. Позвольте мне повторить это еще раз.
Компонент более высокого порядка - это просто функция, которая принимает компонент и улучшает его, передавая ему какое-то поведение в качестве свойств. Затем компонент высшего порядка возвращает этот новый улучшенный компонент для использования.
Теперь посмотрим, как это повлияет на ClickCounter и HoverCounter.
ClickCounter
import React from 'react'
import withCounter from './withCounter'
const ClickCounter = ({count, incrementCount}) => {
return (
<button onClick ={incrementCount}>
Clicked {count} times
</button>
)
}
export default withCounter(ClickCounter)
HoverCounter
import React from 'react'
import withCounter from './withCounter'
const HoverCounter = ({count, incrementCount}) => {
return (
<button onMouseOver ={incrementCount}>
Hovered {count} times
</button>
)
}
export default withCounter(HoverCounter)
Посмотрите, насколько он чище по сравнению с аналогами без withCounter HOC.
Все, что нам нужно сделать, это импортировать withCounter в каждый компонент, а внизу обернуть наш экспорт withCounter. Это позволит экспортировать расширенную версию нашего компонента.
Затем все, что мы делаем, это деструктурируем наши реквизиты, предоставляя нам доступ к count и incrementCount в теле нашего JSX, и все готово!
Убедитесь, что ваш файл App.js выглядит так, чтобы вы могли его протестировать:
App.js
function App() {
return (
<div className="App">
<ClickCounter/>
<HoverCounter/>
</div>
);
}
export default App;

И вот оно! Вот ссылка на мой репозиторий GitHub, если вы хотите получить доступ к любому коду из этой статьи, чтобы поиграть с собой!