Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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
- Legacy code - старые проекты без Hooks
- Static методы - если компонент имеет static методы
- Очень сложная логика - при необходимости обернуть полностью
Частые ошибки с 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 коде и для очень сложных сценариев.