← Назад к вопросам

Приведи пример написания кастомного HOC

2.0 Middle🔥 182 комментариев
#React#Архитектура и паттерны

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Пример написания кастомного HOC

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

Что такое HOC и зачем он нужен?

HOC позволяет:

  • Переиспользовать логику между несколькими компонентами
  • Манипулировать props - добавлять, удалять или трансформировать их
  • Перехватывать методы жизненного цикла (в классовых компонентах)
  • Управлять состоянием для оборачиваемого компонента
  • Рендерить элементы условно в зависимости от логики

Практический пример: HOC для аутентификации

Вот реальный пример HOC, который проверяет аутентификацию пользователя и перенаправляет на страницу входа, если он не авторизован:

// withAuth.js - HOC для защиты маршрутов
function withAuth(WrappedComponent) {
  return function AuthComponent(props) {
    const [isLoading, setIsLoading] = React.useState(true);
    const [isAuthenticated, setIsAuthenticated] = React.useState(false);

    React.useEffect(() => {
      // Проверяем аутентификацию при монтировании
      const checkAuth = async () => {
        try {
          const response = await fetch('/api/me');
          if (response.ok) {
            setIsAuthenticated(true);
          } else {
            window.location.href = '/login';
          }
        } catch (error) {
          window.location.href = '/login';
        } finally {
          setIsLoading(false);
        }
      };

      checkAuth();
    }, []);

    if (isLoading) {
      return <div>Loading...</div>;
    }

    if (!isAuthenticated) {
      return null;
    }

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

// Использование
function Dashboard() {
  return <h1>Welcome to Dashboard</h1>;
}

export default withAuth(Dashboard);

Пример 2: HOC для управления темой

HOC, который добавляет функциональность переключения между светлой и тёмной темой:

// withTheme.js
function withTheme(WrappedComponent) {
  return function ThemedComponent(props) {
    const [theme, setTheme] = React.useState('light');

    const toggleTheme = () => {
      setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
    };

    // Добавляем props для темы в оборачиваемый компонент
    return (
      <div className={`theme-${theme}`}>
        <WrappedComponent
          {...props}
          theme={theme}
          toggleTheme={toggleTheme}
        />
      </div>
    );
  };
}

// Использование
function Settings({ theme, toggleTheme }) {
  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={toggleTheme}>Switch Theme</button>
    </div>
  );
}

export default withTheme(Settings);

Пример 3: HOC для подписки на данные

HOC, который подписывает компонент на обновления данных:

// withDataFetching.js
function withDataFetching(url) {
  return function WrappingComponent(WrappedComponent) {
    return function DataFetchingComponent(props) {
      const [data, setData] = React.useState(null);
      const [loading, setLoading] = React.useState(true);
      const [error, setError] = React.useState(null);

      React.useEffect(() => {
        const fetchData = async () => {
          try {
            const response = await fetch(url);
            if (!response.ok) throw new Error('Failed to fetch');
            const result = await response.json();
            setData(result);
          } catch (err) {
            setError(err.message);
          } finally {
            setLoading(false);
          }
        };

        fetchData();
      }, []);

      return (
        <WrappedComponent
          {...props}
          data={data}
          loading={loading}
          error={error}
        />
      );
    };
  };
}

// Использование
function UsersList({ data, loading, error }) {
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default withDataFetching('/api/users')(UsersList);

Практические советы по использованию HOC

1. Копируй статические методы

При оборачивании компонента теряются его статические методы. Используй библиотеку hoist-non-react-statics:

import hoistNonReactStatics from 'hoist-non-react-statics';

function enhance(WrappedComponent) {
  class Enhance extends React.Component { /*...*/ }
  hoistNonReactStatics(Enhance, WrappedComponent);
  return Enhance;
}

2. Правильно называй оборачиваемый компонент

function enhance(WrappedComponent) {
  class Enhance extends React.Component { /*...*/ }
  Enhance.displayName = `Enhance(${getDisplayName(WrappedComponent)})`;
  return Enhance;
}

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

3. Композиция HOC

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

const EnhancedComponent = withTheme(withAuth(Dashboard));

Альтернатива: Custom Hooks

В современном React HOC часто заменяют кастомными хуками, которые проще для понимания:

// useAuth.js - кастомный хук вместо HOC
function useAuth() {
  const [isAuthenticated, setIsAuthenticated] = React.useState(false);

  React.useEffect(() => {
    fetch('/api/me')
      .then(res => {
        if (res.ok) setIsAuthenticated(true);
      });
  }, []);

  return isAuthenticated;
}

// Использование в компоненте
function Dashboard() {
  const isAuth = useAuth();
  if (!isAuth) return <Redirect to="/login" />;
  return <h1>Dashboard</h1>;
}

HOC - мощный инструмент, но для функциональных компонентов рекомендуется использовать кастомные хуки, так как они более интуитивны и легче отлаживаются.