Как создать HOC в React?react-45

Что такое HOC?

Higher-Order Component (HOC, компонент высшего порядка) — это функция, которая:

  1. Принимает React-компонент в качестве аргумента
  2. Возвращает новый улучшенный компонент
  3. Позволяет повторно использовать логику между компонентами

Базовый шаблон HOC

function withEnhancement(WrappedComponent) {
  // Возвращаем новый компонент
  return function EnhancedComponent(props) {
    // Добавляем новую логику здесь
    const enhancedProps = { ...props, extraData: 'value' };

    // Рендерим обернутый компонент с новыми пропсами
    return <WrappedComponent {...enhancedProps} />;
  };
}

Пошаговый процесс создания

1. Создаем функцию HOC

function withLogger(WrappedComponent) {
  // Возвращаем функциональный компонент
  return function(props) {
    // Добавляем новую функциональность
    console.log(`Рендеринг компонента: ${WrappedComponent.name}`);

    // Прокидываем все пропсы в оригинальный компонент
    return <WrappedComponent {...props} />;
  };
}

2. Используем HOC

const Button = ({ onClick }) => (
  <button onClick={onClick}>Нажми меня</button>
);

const ButtonWithLogger = withLogger(Button);

// Теперь при рендере ButtonWithLogger будет логироваться в консоль

Продвинутые техники

1. HOC с дополнительными аргументами

function withColor(color) {
  return function(WrappedComponent) {
    return function(props) {
      return <WrappedComponent {...props} color={color} />;
    };
  };
}

// Использование
const RedButton = withColor('red')(Button);

2. HOC с доступом к контексту

function withTheme(WrappedComponent) {
  return function(props) {
    const theme = useContext(ThemeContext);
    return <WrappedComponent {...props} theme={theme} />;
  };
}

3. HOC с изменением отображения

function withLoadingIndicator(WrappedComponent) {
  return function({ isLoading, ...props }) {
    return isLoading
      ? <div>Загрузка...</div>
      : <WrappedComponent {...props} />;
  };
}

Обработка displayName

Для удобства отладки добавляем понятное имя:

function withHOC(WrappedComponent) {
  function WithHOC(props) {
    /* логика */
  }

  // Устанавливаем понятное имя для DevTools
  WithHOC.displayName = `WithHOC(${getDisplayName(WrappedComponent)})`;

  return WithHOC;
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

Передача refs

Для корректной работы с ref нужно использовать React.forwardRef:

function withClickTracker(WrappedComponent) {
  function WithClickTracker(props, ref) {
    const handleClick = () => {
      console.log('Компонент был кликнут!');
    };

    return <WrappedComponent ref={ref} {...props} onClick={handleClick} />;
  }

  return React.forwardRef(WithClickTracker);
}

Лучшие практики

  1. Не мутируйте оригинальный компонент — используйте композицию
  2. Копируйте все пропсы — не забывайте про {...props}
  3. Избегайте HOC в render — это сбрасывает состояние компонента
  4. Используйте статические методы — копируйте статики при необходимости
    function copyStaticProperties(source, target) {
      Object.keys(source).forEach(key => {
        target[key] = source[key];
      });
    }
    

Проблемы и решения

1. Потеря статических методов

Решение: копировать вручную или использовать hoist-non-react-statics

2. Конфликты имен пропсов

Решение: тщательно выбирать имена добавляемых пропсов

3. Сложность отладки

Решение: правильно настраивать displayName


Альтернативы

Для новых проектов лучше использовать:

  1. Кастомные хуки (для логики)
  2. Компоненты-провайдеры (для контекста)
  3. Компоненты как children (для композиции)

Пример с хуком:

function useLogger(componentName) {
  useEffect(() => {
    console.log(`Смонтирован: ${componentName}`);
  }, [componentName]);
}

// В компоненте
function Button() {
  useLogger('Button');
  return <button>Click</button>;
}

Резюмируем

  1. HOC создается как функция, принимающая компонент и возвращающая новый
  2. Основная цель — переиспользование логики между компонентами
  3. Важно правильно обрабатывать пропсы, refs и статические методы
  4. Для отладки задавайте понятные displayName
  5. В новых проектах предпочтительнее использовать хуки

Пример полного HOC:

function withAnalytics(WrappedComponent) {
  function WithAnalytics(props) {
    const trackEvent = (eventName) => {
      console.log(`Событие: ${eventName}`);
    };

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

  WithAnalytics.displayName = `WithAnalytics(${getDisplayName(WrappedComponent)})`;

  return WithAnalytics;
}

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

  • Логирования
  • Контроля доступа
  • Загрузки данных
  • Управления состоянием