Подробное объяснение системы 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, если вы хотите получить доступ к любому коду из этой статьи, чтобы поиграть с собой!