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

Как работает HOC?

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

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

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

Higher-Order Component (HOC) в React

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

Основная концепция

// HOC — это функция
const EnhancedComponent = higherOrderComponent(OriginalComponent);

// Где higherOrderComponent выглядит так:
function higherOrderComponent(Component) {
  return function EnhancedComponent(props) {
    // Добавляем функциональность
    return <Component {...props} />;
  };
}

Пример 1: HOC для логирования

// HOC который логирует пропсы
function withLogger(Component) {
  return function EnhancedComponent(props) {
    useEffect(() => {
      console.log('Component mounted:', Component.name);
      console.log('Props:', props);
      
      return () => {
        console.log('Component unmounted:', Component.name);
      };
    }, [props]);
    
    return <Component {...props} />;
  };
}

// Использование
function MyComponent({ name }) {
  return <div>Hello, {name}!</div>;
}

const LoggedComponent = withLogger(MyComponent);

// Теперь при использовании LoggedComponent все пропсы будут логироваться
<LoggedComponent name="Alice" />

Пример 2: HOC для подключения к Redux

// Классический паттерн — подключение компонента к Redux
function withRedux(Component) {
  return function EnhancedComponent(props) {
    const dispatch = useDispatch();
    const data = useSelector(state => state.data);
    
    return (
      <Component 
        {...props}
        data={data}
        dispatch={dispatch}
      />
    );
  };
}

// Использование
function UserList({ data, dispatch }) {
  return (
    <div>
      {data.users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

const ConnectedUserList = withRedux(UserList);

Пример 3: HOC для защиты маршрутов (Authentication)

// HOC для проверки аутентификации
function withAuth(Component) {
  return function EnhancedComponent(props) {
    const { isAuthenticated, loading } = useAuth();
    
    if (loading) return <div>Загрузка...</div>;
    
    if (!isAuthenticated) {
      return <Navigate to="/login" />;
    }
    
    return <Component {...props} />;
  };
}

// Использование
function Dashboard() {
  return <div>Защищённая страница</div>;
}

const ProtectedDashboard = withAuth(Dashboard);

// Теперь ProtectedDashboard доступна только для аутентифицированных пользователей

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

function withTheme(Component) {
  return function EnhancedComponent(props) {
    const [theme, setTheme] = useState('light');
    
    const toggleTheme = () => {
      setTheme(prev => prev === 'light' ? 'dark' : 'light');
    };
    
    return (
      <div className={`theme-${theme}`}>
        <Component 
          {...props}
          theme={theme}
          toggleTheme={toggleTheme}
        />
      </div>
    );
  };
}

// Использование
function App({ theme, toggleTheme }) {
  return (
    <div>
      <p>Текущая тема: {theme}</p>
      <button onClick={toggleTheme}>Переключить тему</button>
    </div>
  );
}

const ThemedApp = withTheme(App);

Пример 5: HOC для данных (Data Fetching)

// HOC который подгружает данные
function withDataFetching(url) {
  return function(Component) {
    return function EnhancedComponent(props) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
      
      useEffect(() => {
        const fetchData = async () => {
          try {
            const response = await fetch(url);
            const result = await response.json();
            setData(result);
          } catch (err) {
            setError(err.message);
          } finally {
            setLoading(false);
          }
        };
        
        fetchData();
      }, []);
      
      return (
        <Component 
          {...props}
          data={data}
          loading={loading}
          error={error}
        />
      );
    };
  };
}

// Использование
function PostList({ data, loading, error }) {
  if (loading) return <div>Загрузка...</div>;
  if (error) return <div>Ошибка: {error}</div>;
  
  return (
    <ul>
      {data.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

const PostListWithData = withDataFetching('/api/posts')(PostList);

Пример 6: HOC для валидации пропсов

// HOC который валидирует пропсы
function withPropValidation(Component, requiredProps) {
  return function EnhancedComponent(props) {
    const missingProps = requiredProps.filter(prop => !(prop in props));
    
    if (missingProps.length > 0) {
      return (
        <div style={{ color: 'red' }}>
          Ошибка: отсутствуют пропсы: {missingProps.join(', ')}
        </div>
      );
    }
    
    return <Component {...props} />;
  };
}

// Использование
function UserProfile({ userId, userName }) {
  return <div>{userName} (ID: {userId})</div>;
}

const ValidatedUserProfile = withPropValidation(
  UserProfile, 
  ['userId', 'userName']
);

// Если не передать userId или userName — будет ошибка
<ValidatedUserProfile userId={1} /> // Ошибка: отсутствуют пропсы: userName

Композиция HOC

Можно применять несколько HOC'ов к одному компоненту:

// Применение нескольких HOC'ов
const EnhancedComponent = withTheme(
  withLogger(
    withAuth(MyComponent)
  )
);

// Или с помощью утилиты compose
const compose = (...hocs) => (component) => 
  hocs.reduceRight((comp, hoc) => hoc(comp), component);

const EnhancedComponent = compose(
  withTheme,
  withLogger,
  withAuth
)(MyComponent);

// Порядок важен! Внутренние HOC'ы выполняются первыми

Правильные имена

// ✅ Хорошо: имя отражает, что HOC делает
function withSubscription(Component) { ... }
function withDataFetching(url) { ... }
function withTheme(Component) { ... }

// ❌ Плохо: непонятное имя
function enhance(Component) { ... }
function wrapper(Component) { ... }

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

// ⚠️ Проблема: статические методы HOC'а не копируются
function MyComponent() { ... }
MyComponent.staticMethod = () => 'Hello';

const Enhanced = withSomeHOC(MyComponent);
Enhanced.staticMethod(); // undefined!

// ✅ Решение: используй hoist-non-react-statics
import hoistNonReactStatics from 'hoist-non-react-statics';

function withSomeHOC(Component) {
  function EnhancedComponent(props) {
    return <Component {...props} />;
  }
  
  hoistNonReactStatics(EnhancedComponent, Component);
  return EnhancedComponent;
}

Refs и HOC

// ⚠️ Проблема: refs не пробрасываются
const EnhancedComponent = withSomeHOC(MyComponent);
const ref = React.createRef();
<EnhancedComponent ref={ref} />; // ref не пройдёт в MyComponent

// ✅ Решение: используй React.forwardRef
function withSomeHOC(Component) {
  const EnhancedComponent = React.forwardRef((props, ref) => {
    return <Component {...props} forwardedRef={ref} />;
  });
  
  EnhancedComponent.displayName = `WithHOC(${Component.displayName})`;
  return EnhancedComponent;
}

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

  1. Не используй HOC в render методе — это создаёт новый компонент при каждом рендере
// ❌ Плохо
function App() {
  const Enhanced = withSomeHOC(MyComponent); // новый компонент каждый раз
  return <Enhanced />;
}

// ✅ Хорошо
const Enhanced = withSomeHOC(MyComponent);

function App() {
  return <Enhanced />;
}
  1. Копируй статические методы через hoist-non-react-statics
  2. Пробрасывай refs через forwardRef
  3. Давай понятные имена компонентам
  4. Документируй какие пропсы добавляет HOC

HOC vs Hooks

Сейчас HOC часто заменяют Custom Hooks, которые проще и понятнее:

// Вместо HOC
const ConnectedComponent = withRedux(MyComponent);

// Используй Hook
function MyComponent() {
  const data = useSelector(state => state.data);
  const dispatch = useDispatch();
  // ...
}

Однако HOC всё ещё полезны для:

  • Обёртывания в провайдеры
  • Защиты маршрутов
  • Предоставления контекста
  • Сложной трансформации пропсов

HOC — это мощный паттерн для переиспользования логики, но в современном React рекомендуется использовать Custom Hooks как первый вариант, а HOC как второй, для более сложных случаев.

Как работает HOC? | PrepBro