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

Для чего нужны HOC?

2.0 Middle🔥 181 комментариев
#Soft Skills и рабочие процессы

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

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

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

HOC (Higher-Order Components): переиспользование логики компонентов

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

Определение

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

// Или в TypeScript
const withLogger = <P extends object>(Component: React.ComponentType<P>) => 
  (props: P) => {
    console.log('Component mounted:', Component.name);
    return <Component {...props} />;
  };

Зачем нужны HOC

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

Есть логика, которая требуется нескольким компонентам:

// Логика: подписаться на тему из Context при монтировании

// БЕЗ HOC - повторяем в каждом компоненте
function UserProfile({ userId }) {
  const theme = useContext(ThemeContext);
  
  useEffect(() => {
    console.log('User profile mounted with theme:', theme);
  }, [theme]);
  
  return <div className={theme}>User: {userId}</div>;
}

function UserSettings({ userId }) {
  const theme = useContext(ThemeContext);
  
  useEffect(() => {
    console.log('User settings mounted with theme:', theme);
  }, [theme]);
  
  return <div className={theme}>Settings for: {userId}</div>;
}

// Дублирование!

С HOC - извлекаем логику

// HOC извлекает повторяющуюся логику
const withThemeLogging = (Component) => {
  return (props) => {
    const theme = useContext(ThemeContext);
    
    useEffect(() => {
      console.log(`${Component.name} mounted with theme:`, theme);
    }, [theme]);
    
    return <Component {...props} theme={theme} />;
  };
};

// Используем в компонентах
const UserProfileWithLogging = withThemeLogging(UserProfile);
const UserSettingsWithLogging = withThemeLogging(UserSettings);

2. Props манипуляция

Некоторые props нужно преобразить перед передачей в компонент:

// HOC для преобразования props
const withUserData = (Component) => {
  return (props) => {
    const { userId } = props;
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);
    
    useEffect(() => {
      fetchUser(userId).then((data) => {
        setUser(data);
        setLoading(false);
      });
    }, [userId]);
    
    return (
      <Component 
        {...props} 
        user={user}
        loading={loading}
      />
    );
  };
};

function UserDisplay({ user, loading }) {
  if (loading) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}

const UserDisplayWithData = withUserData(UserDisplay);

3. Состояние (State abstraction)

Извлечение состояния из компонента в HOC:

// HOC управляет состоянием
const withFormState = (Component) => {
  return (props) => {
    const [formData, setFormData] = useState({});
    
    const handleChange = (e) => {
      const { name, value } = e.target;
      setFormData(prev => ({ ...prev, [name]: value }));
    };
    
    const handleSubmit = async (e) => {
      e.preventDefault();
      // Обработка
      console.log('Form submitted:', formData);
    };
    
    return (
      <Component
        {...props}
        formData={formData}
        handleChange={handleChange}
        handleSubmit={handleSubmit}
      />
    );
  };
};

function LoginForm({ formData, handleChange, handleSubmit }) {
  return (
    <form onSubmit={handleSubmit}>
      <input
        name="email"
        value={formData.email || ''}
        onChange={handleChange}
      />
      <button type="submit">Login</button>
    </form>
  );
}

const LoginFormWithState = withFormState(LoginForm);

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

Ограничение доступа к компонентам:

// HOC для защиты маршрутов
const withAuth = (Component) => {
  return (props) => {
    const { user, loading } = useAuth();
    
    if (loading) return <div>Loading...</div>;
    
    if (!user) {
      return <Navigate to="/login" />;
    }
    
    return <Component {...props} user={user} />;
  };
};

function AdminPanel({ user }) {
  return <div>Welcome {user.name}</div>;
}

const ProtectedAdminPanel = withAuth(AdminPanel);

5. Themes и внешний вид

Передача тематических данных компонентам:

const withTheme = (Component) => {
  return (props) => {
    const theme = useContext(ThemeContext);
    
    return (
      <div className={`theme-${theme}`}>
        <Component {...props} theme={theme} />
      </div>
    );
  };
};

function Button({ theme, children }) {
  const bgClass = theme === 'dark' ? 'bg-gray-900' : 'bg-white';
  return <button className={bgClass}>{children}</button>;
}

const ThemedButton = withTheme(Button);

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

const withLogger = (Component) => {
  const WrappedComponent = (props) => {
    useEffect(() => {
      console.log(`${Component.name} mounted`);
      
      return () => {
        console.log(`${Component.name} unmounted`);
      };
    }, []);
    
    return <Component {...props} />;
  };
  
  // Важно: установить displayName для debub-выводе
  WrappedComponent.displayName = `withLogger(${Component.displayName || Component.name})`;
  
  return WrappedComponent;
};

// Использование
const UserProfileWithLogging = withLogger(UserProfile);

HOC vs Render Props vs Hooks

Когда использовать что

// HOC - для старых проектов и сложной логики
const Enhanced = withLogic(Component);

// Render Props - для гибкости
<DataProvider>
  {(data) => <Component data={data} />}
</DataProvider>

// Hooks (ПРЕДПОЧТИТЕЛЬНО) - для современного React
const Component = () => {
  const data = useCustomHook();
  return <div>{data}</div>;
};

Лучше использовать Hooks!

В современном React HOC часто заменяются на Hooks

// HOC подход (старый)
const withDataFetching = (Component) => {
  return (props) => {
    const [data, setData] = useState(null);
    useEffect(() => {
      fetch(props.url).then(r => r.json()).then(setData);
    }, [props.url]);
    return <Component {...props} data={data} />;
  };
};

const ComponentWithData = withDataFetching(MyComponent);

// Hooks подход (новый) - ПРЕДПОЧТИТЕЛЬНЕЕ
const useDataFetching = (url) => {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch(url).then(r => r.json()).then(setData);
  }, [url]);
  return data;
};

const MyComponent = ({ url }) => {
  const data = useDataFetching(url);
  return <div>{data}</div>;
};

Когда всё ещё нужны HOC

  1. Legacy code - старые проекты без Hooks
  2. Static методы - если компонент имеет static методы
  3. Очень сложная логика - при необходимости обернуть полностью

Частые ошибки с HOC

Ошибка 1: создание HOC внутри render

// НЕПРАВИЛЬНО - создаёт новый HOC при каждом render
function App() {
  const Enhanced = withLogger(Component); // ПЛОХО!
  return <Enhanced />;
}

// ПРАВИЛЬНО - создаёт один раз
const Enhanced = withLogger(Component);
function App() {
  return <Enhanced />;
}

Ошибка 2: забывают передать refs

// НЕПРАВИЛЬНО - ref не передаётся
const withLogic = (Component) => {
  return (props) => <Component {...props} />; // ref потеряется
};

// ПРАВИЛЬНО
const withLogic = (Component) => {
  return React.forwardRef((props, ref) => {
    return <Component ref={ref} {...props} />;
  });
};

Ошибка 3: изменяют displayName

// ПРАВИЛЬНО - для удобного debug
WrappedComponent.displayName = `withLogic(${Component.displayName || Component.name})`;

Реальный пример: HOC для мобильной адаптации

const withResponsive = (Component) => {
  return (props) => {
    const [isMobile, setIsMobile] = useState(
      window.innerWidth < 768
    );
    
    useEffect(() => {
      const handleResize = () => {
        setIsMobile(window.innerWidth < 768);
      };
      
      window.addEventListener('resize', handleResize);
      return () => window.removeEventListener('resize', handleResize);
    }, []);
    
    return <Component {...props} isMobile={isMobile} />;
  };
};

function Navigation({ isMobile }) {
  if (isMobile) {
    return <MobileNav />;
  }
  return <DesktopNav />;
}

const ResponsiveNav = withResponsive(Navigation);

Заключение

HOC (Higher-Order Components) - это паттерн для переиспользования логики компонентов. Они полезны для:
  • Извлечения повторяющейся логики
  • Props манипуляции
  • State abstraction
  • Аутентификации и защиты

Однако в современном React Hooks являются предпочтительным подходом для большинства случаев. HOC остаются полезны в legacy коде и для очень сложных сценариев.