Вокруг Реакта сложилось достаточно много паттернов, один из них — компоненты высшего порядка. Они призваны сделать удобным реиспользование общей функциональности между компонентами: эта функциональность выносится в компонент высшего порядка, а компоненты, требующие её, перед использованием просто оборачиваются в HOC. В теории звучит здорово, но на практике у компонентов высшего порядка есть большой недостаток: они похожи на наследование.
Концептуальная проблема
В чём проблема наследования? В том, что каждый уровень в иерархии классов влияет на следующие уровни. Метод любого класса будет распространён на все дочерние классы, которые, в свою очередь, объявляют собственные методы и могут переопределять унаследованные. Чтобы понять, что доступно в конкретном классе, нужно держать в голове всю цепочку родительских классов с их собственными и переопределенными методами.
Это и есть общая проблема наследования и компонентов высшего порядка: на конкретную программную сущность (класс или компонент) влияют внешние сущности (родительский класс или компонент высшего порядка), и чтобы ей корректно пользоваться, разработчику приходится знать и помнить результаты этого влияния.
У каждого компонента в Реакте есть свой API, называемый пропами. Пропы — мост между компонентом и внешним миром. Любой компонент высшего порядка — дополнительный слой между обёрнутым компонентом и внешним миром, который влияет на обе стороны: он может передавать какие-то пропы обёрнутому компоненту или ожидать какие-то пропы от внешнего мира. В итоге разработчик вынужден помнить об этом неявном изменении контракта между обёрнутым компонентом и внешним миром.
Альтернатива: рендер-пропы
Компоненты высшего порядка — не единственный паттерн для реиспользования общей функциональности. Общую функциональность можно вынести в отдельный компонент и сделать её доступной через рендер-пропы: при таком подходе любые данные передаются явно и ничего не нарушает контракт компонентов с внешним миром. Пример (код максимально упрощён и далёк от реальных кейсов в угоду наглядности):
/**
* Подход с компонентами высшего порядка
*/
import { message } from 'antd';
const Button = (props) => <button {...props} />;
const withMessage = (WrappedComponent) => ({
messageType,
messageText,
...restProps
}) => (
<WrappedComponent
{...restProps}
onClick={() => message[messageType](messageText)}
/>
);
const ButtonWithMessage = withMessage(Button);
<ButtonWithMessage messageType="success" messageText="Hello!">
Say hello
</ButtonWithMessage>;
/**
* Подход с рендер-пропами
*/
import { message } from 'antd';
const Button = (props) => <button {...props} />;
const MessageProvider = ({ type, text, children }) =>
children({ showMessage: () => message[type](text) });
<MessageProvider type="success" text="hello">
{({ showMessage }) => <Button onClick={showMessage}>Say hello</Button>}
</MessageProvider>;