HOC расшифровывается как Higher-Order Components.
Это означает компонент, который принимает другой компонент и возвращает улучшенный компонент.
const HoC = Component => EnhancedComponent
Основная цель использования HOC — иметь единое место для обмена функциональными возможностями между компонентами.
(Думайте о HOC скорее как о функции, чем как о компоненте, это будет более понятно по ходу дела).
Предположим, у вас есть требование вычислить внутреннюю ширину области просмотра браузера при изменении ее размеров, а затем выполнить некоторые вычисления/изменение интерфейса.
В нашем примере мы просто хотим отображать window.innerWidth в «реальном времени».
Для этого мы можем написать функциональный компонент, использовать хук useEffect и подключить функцию-слушатель, которая будет обновлять переменную состояния.
Смотрите код: https://github.com/abhidatta0/react-hoc-in-typescript/blob/master/src/ResizeComponent.tsx
Теперь предположим, что нам нужно использовать эту логику в другом компоненте. В этом случае нам придется дублировать нашу логику 😞 (что, очевидно, плохо).
Что может быть лучше (или лучше)?
Мы можем просто абстрагироваться от этого кода useEffect и listener в другой компонент и раскрыть переменную innerWidth как prop.
Этот «специальный» компонент и есть наш компонент высшего порядка.
withResize.tsx
const withResize = (Component: FC<any> )=> (props: any)=> {
const [innerWidth, setInnerWidth] = useState(0);
const handleResize = ()=>{
setInnerWidth(window.innerWidth);
}
useEffect(()=>{
window.addEventListener('resize', handleResize);
return ()=>{
window.removeEventListener('resize', handleResize);
}
},[]);
return <Component {...props} windowInnerWidth={innerWidth} />
}
WithResizeUsage.tsx
const WithResizeUsage = ({name, windowInnerWidth}: {name: string, windowInnerWidth: number})=>{
return (
<div>Inner Width is {windowInnerWidth} and name is {name}</div>
)
}
export default withResize(WithResizeUsage);
App.tsx
<WithResizeUsage name="Developer" />
Давайте проанализируем то, что мы написали
- В App.tsx мы вызываем WithResizeUsage с именем prop.
- WithResize — это специальная функция, которая принимает Компонент, который сам принимает некоторые реквизиты.
- Возвратом withResize является компонент со всеми его исходными реквизитами и дополнительным реквизитом
windowInnerWidth
. - В WithResizeUsage: мы принимаем реквизит
name
, который мы получаем из App.tsx иwindowInnerWidth
, который мы получаем изwithResize
hoc. - В WithResizeUsage мы оборачиваем его withResize в последней строке
withResize(WithResizeUsage)
, чтобы связать его с withResize.
См. код:
https://github.com/abhidatta0/react-hoc-in-typescript/blob/master/src/withResize.tsx
https://github.com/abhidatta0/react-hoc-in-typescript/blob/master/src/WithResizeUsage.tsx
Обратите внимание, насколько чистым стал наш компонент WithResizeUsage.
Давайте посмотрим, как мы можем еще немного улучшить HOC.
Предположим, нам нужно, чтобы отображаемая ширина window.innerWidth была на x больше, причем значение x будет настраиваемым. Если внутренняя ширина равна 90, а x равно 3, мы отобразим 93.
Мы изменим HOC, добавив еще один уровень для приема дополнительных параметров. (Эта концепция называется функцией currying).
withResizeAdvanced.tsx
type Params = {
bumped: number;
}
const withResizeAdvanced = (params: Params)=> (Component: FC<any> )=> (props: any)=> {
console.log(params);
const [innerWidth, setInnerWidth] = useState(0);
const handleResize = ()=>{
setInnerWidth(window.innerWidth+params.bumped);
}
useEffect(()=>{
window.addEventListener('resize', handleResize);
return ()=>{
window.removeEventListener('resize', handleResize);
}
},[]);
return <Component {...props} windowInnerWidth={innerWidth} />
}
withResizeAdvancedUsage.tsx
import withResizeAdvanced from "./withResizeAdvanced";
const WithResizeUsage = ({name, windowInnerWidth}: {name: string, windowInnerWidth: number})=>{
return (
<div>Inner Width is {windowInnerWidth} and name is {name}</div>
)
}
export default withResizeAdvanced({bumped: 5})(WithResizeUsage);
App.tsx
<WithResizeAdvancedUsage name="Developer"/>
Все аналогично предыдущему HOC, за исключением того, что мы можем передать объект, который имеет свойство bumped
, которое мы можем использовать для изменения innerWidth.
Если мы поместим более ранний и новый компонент в App.tsx, то сможем увидеть, что для более позднего компонента innerWidth увеличивается на 5.
Github repo: https://github.com/abhidatta0/react-hoc-in-typescript
Примечание:
- Убедитесь, что HOC имеет общую логику (например, получение window.innerWidth), которая будет повторно использоваться в других компонентах.
- Альтернативой шаблону HOC для функционального компонента является использование пользовательских хуков.