Что такое HOC (Higher-Order Component)? Когда его использование оправдано?

HOC (Higher-Order Component, компонент высшего порядка) — это функция, которая принимает компонент и возвращает новый компонент с расширенным функционалом. Это один из популярных паттернов в React, вдохновлённый функциональным программированием. HOC позволяет повторно использовать логику между компонентами без дублирования кода.

Синтаксис и суть HOC

HOC — это не React API, а паттерн, основанный на идее:

const EnhancedComponent = higherOrderComponent(WrappedComponent);

Простейший пример:

function withLogger(WrappedComponent) {
return function EnhancedComponent(props) {
console.log('Props:', props);
return <WrappedComponent {...props} />;
};
}
  • withLogger — HOC.

  • WrappedComponent — оригинальный компонент.

  • EnhancedComponent — новый компонент, который логирует пропсы и передаёт их дальше.

Где используется HOC

  1. Повторное использование логики

  2. Аутентификация/авторизация

  3. Управление доступом

  4. Хендлинг ошибок

  5. Подключение к внешним источникам данных (API, Redux)

  6. Логирование, отслеживание, профилирование

  7. Кэширование, мемоизация

Реальный пример: withAuth HOC

HOC, который проверяет, авторизован ли пользователь, и в противном случае делает редирект:

import { useRouter } from 'next/router';
function withAuth(WrappedComponent) {
return function ProtectedRoute(props) {
const router = useRouter();
const isAuthenticated = typeof window !== 'undefined' && localStorage.getItem('token');
if (!isAuthenticated) {
router.push('/login');
return null;
}
return <WrappedComponent {...props} />;
};
}

Особенности реализации HOC:

1. Передача пропсов

Важно передавать все пропсы внутрь WrappedComponent, чтобы не потерять их:

return <WrappedComponent {...props} />;

2. Копирование displayName

Для дебага полезно сохранять имя компонента:

WrappedComponent.displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
EnhancedComponent.displayName = \`withAuth(${WrappedComponent.displayName})\`;

3. Изоляция состояний

HOC создаёт новый компонент, но WrappedComponent остаётся неизменным. Это делает HOC хорошим способом «обёртки» без модификации оригинального компонента.

HOC против других подходов

Подход Когда использовать Пример
HOC Повторная логика UI/контроля, логирования withLogger, withAuth
--- --- ---
Render Props Поведение завязано на child-функцию <DataProvider>{(data) => ...}
--- --- ---
Custom Hooks Повторная логика без влияния на структуру JSX useFetch, useAuth
--- --- ---
Контекст (Context) Глобальное хранилище ThemeContext, UserContext
--- --- ---
Компоненты-композиции Гибкое построение на уровне children <Card><CardBody/></Card>
--- --- ---

Примеры HOC в популярных библиотеках:

connect() из react-redux — подключает компонент к Redux store:

```python
import { connect } from 'react-redux';
const mapStateToProps = (state) => ({
user: state.user,
});
export default connect(mapStateToProps)(UserProfile);

- withRouter (до React Router v6):  
    Оборачивает компонент, давая доступ к параметрам маршрута.  

- withFormik, withTheme, withTranslation  HOC из библиотек для интеграции с формами, темами и i18n.  


### **Композиция HOC**

Можно оборачивать компонент несколькими HOC:

```python
export default withLogger(withAuth(withTheme(MyComponent)));

Или использовать compose():

import { compose } from 'redux';
export default compose(
withAuth,
withLogger,
withTheme
)(MyComponent);

Проблемы и ограничения HOC:

  1. Глубокое оборачивание (wrapper hell):
    Может быть трудно отследить, какие HOC были применены.

  2. Конфликты пропсов:
    HOC может передавать/изменять пропсы, что нарушит изоляцию логики.

  3. Сложность TypeScript-типизации:
    Требуется дженерики для сохранения типов пропсов WrappedComponent.

Пример типизации HOC на TypeScript:

function withLogger&lt;P&gt;(Component: React.ComponentType&lt;P&gt;) {
return function LoggedComponent(props: P) {
console.log('Props:', props);
return &lt;Component {...props} /&gt;;
};
}

Когда HOC особенно оправдан:

  • Когда нужна повторяемая логика, которую можно инкапсулировать без модификации компонента.

  • Когда нельзя использовать hooks (например, ты работаешь с class-компонентами).

  • Когда нужно сохранять совместимость со сторонними библиотеками, которые используют HOC.

  • При кросс-сечениях логики: например, один компонент требует и авторизации, и логирования, и доступа к данным.

  • Когда важно отделить презентацию от логики (pattern container/presenter).

HOC остаются мощным инструментом в React, особенно при работе со сторонними библиотеками, переиспользуемыми слоями логики или необходимостью декорировать компоненты. В новых проектах чаще предпочтение отдают кастомным хукам и серверным компонентам (Next.js App Router), но HOC по-прежнему остаётся актуальным инструментом в арсенале React-разработчика.