Higher-Order Component (HOC, компонент высшего порядка) — это функция, которая:
function withEnhancement(WrappedComponent) {
// Возвращаем новый компонент
return function EnhancedComponent(props) {
// Добавляем новую логику здесь
const enhancedProps = { ...props, extraData: 'value' };
// Рендерим обернутый компонент с новыми пропсами
return <WrappedComponent {...enhancedProps} />;
};
}
function withLogger(WrappedComponent) {
// Возвращаем функциональный компонент
return function(props) {
// Добавляем новую функциональность
console.log(`Рендеринг компонента: ${WrappedComponent.name}`);
// Прокидываем все пропсы в оригинальный компонент
return <WrappedComponent {...props} />;
};
}
const Button = ({ onClick }) => (
<button onClick={onClick}>Нажми меня</button>
);
const ButtonWithLogger = withLogger(Button);
// Теперь при рендере ButtonWithLogger будет логироваться в консоль
function withColor(color) {
return function(WrappedComponent) {
return function(props) {
return <WrappedComponent {...props} color={color} />;
};
};
}
// Использование
const RedButton = withColor('red')(Button);
function withTheme(WrappedComponent) {
return function(props) {
const theme = useContext(ThemeContext);
return <WrappedComponent {...props} theme={theme} />;
};
}
function withLoadingIndicator(WrappedComponent) {
return function({ isLoading, ...props }) {
return isLoading
? <div>Загрузка...</div>
: <WrappedComponent {...props} />;
};
}
Для удобства отладки добавляем понятное имя:
function withHOC(WrappedComponent) {
function WithHOC(props) {
/* логика */
}
// Устанавливаем понятное имя для DevTools
WithHOC.displayName = `WithHOC(${getDisplayName(WrappedComponent)})`;
return WithHOC;
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
Для корректной работы с 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);
}
{...props}
function copyStaticProperties(source, target) {
Object.keys(source).forEach(key => {
target[key] = source[key];
});
}
Решение: копировать вручную или использовать hoist-non-react-statics
Решение: тщательно выбирать имена добавляемых пропсов
Решение: правильно настраивать displayName
Для новых проектов лучше использовать:
Пример с хуком:
function useLogger(componentName) {
useEffect(() => {
console.log(`Смонтирован: ${componentName}`);
}, [componentName]);
}
// В компоненте
function Button() {
useLogger('Button');
return <button>Click</button>;
}
Пример полного 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 для: