Как соблюдать принцип открытости/закрытости в UI: расширять компонент без изменения его кода?
SOLID во фронтенде
OCP в UI: компонент расширяют через children, слоты-пропсы, render-функции и конфигурацию, а не добавляя в него новый булев флаг под каждый случай.
Антипаттерн
<Modal showFooter isDanger withCloseIcon hasSecondaryButton /> — каждый новый макет даёт новый проп и новую ветку if внутри. Флаги комбинируются в 2^n состояний, большинство из которых никто не проверял.
Способы расширения
- children — потребитель сам решает, что внутри.
- Слоты — именованные пропсы типа
ReactNode:header,footer,icon. - Render-функции — когда слоту нужны данные компонента:
renderItem(item, index). - Конфигурация/реестр —
columns: ColumnDef[]или карта{ [type]: Component }: новый тип поля формы — новая запись, а не правкаswitch. - Compound components —
<Tabs.List>,<Tabs.Panel>с общим контекстом.
/** @param {{ title: ReactNode, footer?: ReactNode, children: ReactNode }} props */
function Card({ title, footer, children }) {
return (
<section className="card">
<header>{title}</header>
<div>{children}</div>
{footer && <footer>{footer}</footer>}
</section>
);
}Граница
Слишком открытый компонент ломает консистентность дизайн-системы: если в footer кладут что угодно, кнопки разъезжаются. Открывают только оси, где вариативность нужна на деле.
Коротко для собеседования
Компонент открывают через children, слоты, render-пропсы и конфигурацию, а не копят булевы флаги с ветками if. Предельный случай — headless-библиотеки вроде Radix, отдающие потребителю всю разметку; открывать стоит только оси реальной вариативности, иначе дизайн-система расползается.