Что такое Higher Order Component (HOC) в React?
Higher Order Component (HOC)
Higher Order Component (HOC) — это паттерн в React, основанный на концепции функций высшего порядка из функционального программирования. HOC — это функция, которая принимает компонент как аргумент и возвращает новый компонент, добавляя к нему дополнительное поведение или данные.
Основная идея
HOC позволяет выносить общую логику в одно место и переиспользовать её между разными компонентами. Это особенно полезно, когда несколько компонентов нуждаются в одинаковом поведении: проверке аутентификации, логировании, работе с данными из хранилища.
// Простой HOC, добавляющий логирование
function withLogging<T extends object>(WrappedComponent: React.ComponentType<T>) {
return function LoggedComponent(props: T) {
console.log('Рендер компонента:', WrappedComponent.displayName);
return <WrappedComponent {...props} />;
};
}
Типичные сценарии применения
Защита маршрутов (авторизация):
function withAuth<T extends object>(WrappedComponent: React.ComponentType<T>) {
return function AuthGuard(props: T) {
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return <WrappedComponent {...props} />;
};
}
// Использование
const ProtectedDashboard = withAuth(Dashboard);
Добавление данных из внешнего источника:
function withUser<T extends { user?: User }>(WrappedComponent: React.ComponentType<T>) {
return function WithUserComponent(props: Omit<T, 'user'>) {
const user = useCurrentUser();
return <WrappedComponent {...(props as T)} user={user} />;
};
}
Важные правила при написании HOC
- Не мутировать оригинальный компонент — всегда возвращать новый
- Пробрасывать все props через
{...props}, чтобы не потерять интерфейс компонента - Устанавливать displayName для читаемой отладки в React DevTools
- Не использовать HOC внутри render — это приведёт к постоянному пересозданию компонента
function withFeature<T extends object>(WrappedComponent: React.ComponentType<T>) {
function EnhancedComponent(props: T) {
return <WrappedComponent {...props} />;
}
// Устанавливаем displayName для DevTools
EnhancedComponent.displayName = `withFeature(${WrappedComponent.displayName || WrappedComponent.name})`;
return EnhancedComponent;
}
HOC vs Hooks
С появлением хуков в React 16.8 многие задачи, которые раньше решались HOC, теперь решаются с помощью кастомных хуков. Хуки проще в отладке, не создают лишних обёрток в дереве компонентов и не имеют проблем с коллизиями имён props. Однако HOC по-прежнему актуальны для:
- Компонентов на классах, где хуки недоступны
- Случаев, когда нужно условно рендерить компонент целиком
- Интеграции со сторонними библиотеками (например,
connectиз React-Redux)
Композиция HOC
HOC можно комбинировать, создавая цепочки:
const EnhancedComponent = withAuth(withLogging(withUser(MyComponent)));
// Или через compose из Redux / Ramda
const enhance = compose(withAuth, withLogging, withUser);
const EnhancedComponent = enhance(MyComponent);
Что хочет услышать интервьюер
Чёткое определение HOC как функции, принимающей компонент и возвращающей новый компонент
Понимание цели HOC — переиспользование логики без дублирования кода
Знание правил: не мутировать компонент, пробрасывать все props, устанавливать displayName
Осознание, что HOC — это паттерн, а не API React, и его связь с функциями высшего порядка
Понимание компромисса между HOC и кастомными хуками: когда что предпочтительнее
Пример: HOC для защиты маршрута (авторизация)
import React from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
function withAuth<T extends object>(WrappedComponent: React.ComponentType<T>) {
function AuthGuard(props: T) {
const isAuthenticated = useSelector(
(state: { auth: { isAuthenticated: boolean } }) => state.auth.isAuthenticated
);
if (!isAuthenticated) {
// Перенаправляем на страницу входа, если пользователь не авторизован
return <Navigate to="/login" replace />;
}
return <WrappedComponent {...props} />;
}
// Улучшаем читаемость в React DevTools
AuthGuard.displayName = `withAuth(${WrappedComponent.displayName || WrappedComponent.name})`;
return AuthGuard;
}
// Оборачиваем компонент дашборда защитой
const ProtectedDashboard = withAuth(Dashboard);
export default ProtectedDashboard;
Пример: Композиция нескольких HOC
import { compose } from 'redux';
// Каждый HOC добавляет одну ответственность
const withLogging = <T extends object>(Component: React.ComponentType<T>) => {
function Logged(props: T) {
console.log(`[Рендер] ${Component.displayName || Component.name}`);
return <Component {...props} />;
}
Logged.displayName = `withLogging(${Component.displayName || Component.name})`;
return Logged;
};
const withErrorBoundary = <T extends object>(Component: React.ComponentType<T>) => {
// ... реализация обработки ошибок
return Component;
};
// Применяем несколько HOC через compose
const enhance = compose(withAuth, withLogging, withErrorBoundary);
const EnhancedProfile = enhance(ProfilePage);
Типичные ошибки
Путают HOC с обычным компонентом-обёрткой — не понимают, что HOC это функция, возвращающая функцию
Забывают пробрасывать props через {...props}, тем самым ломая интерфейс оборачиваемого компонента
Создают HOC внутри метода render или тела функционального компонента, что вызывает пересоздание на каждый рендер
Не устанавливают displayName, что затрудняет отладку в React DevTools
Не знают современных альтернатив — кастомных хуков — и когда HOC всё ещё предпочтительнее


