Композиция и темизация компонентов — Примеры
Пример 1 — Тема, которая переопределяет компонент снаружи
Тёмная тема побеждает стили кнопки более длинным селектором, и селектор уже вырос однажды: скажите, чего стоит переопределение, и дайте теме поставлять значения вместо этого.
.button { background: navy; color: white; padding: 0.5rem 1rem; border-radius: 0.25rem; }
.theme-dark .panel .button { /* побеждает компонент специфичностью */
background: #111;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
Решение
.button {
--control-pad: 0.5rem 1rem;
--control-radius: 0.25rem;
background: var(--button-bg, navy);
color: var(--button-fg, white);
padding: var(--control-pad);
border-radius: var(--control-radius);
}
.theme-dark .panel { --button-bg: #111; --button-fg: #eee; }
<div className="theme-dark">
<div className="panel"><Button variant="primary">Сохранить</Button></div>
</div>
Переопределение побеждает специфичностью, поэтому каждой новой теме нужен селектор длиннее предыдущего, и компонент нельзя отрефакторить, не сломав их. Пользовательские свойства переворачивают направление: компонент читает значение, а тема задаёт его на обёртке, и поскольку свойства наследуются, одна обёртка темизирует всё внутри без селектора на каждый компонент. Запасное значение внутри var() защищает компонент, когда его никто не задал. Всё, что потребитель может задать, — публичный API, поэтому имена свойств документируют и переименовывают только как версионированное изменение.
Пример 2 — Компонент, который скопировали ради одного экрана
Карточку со строкой действий скопировали дважды, потому что в оригинале негде было разместить кнопки. Скажите, почему появилась копия, и расширьте оригинал недостающим слотом.
function Card({ title, body }) {
return (
<article className="card">
<h2>{title}</h2>
<p>{body}</p>
</article>
);
}
// в другом месте: CardWithActions, затем CardWithActionsAndBadge, обе копии
Решение
function Card({ title, body, actions }) {
return (
<article className="card">
<h2>{title}</h2>
<p>{body}</p>
{actions ? <div className="card-actions">{actions}</div> : null}
</article>
);
}
<Card
title="Счёт №1042"
body={summary}
actions={<><Button variant="primary">Оплатить</Button><Button>Скачать</Button></>}
/>
Содержимое, переданное как дочерние элементы или слот, можно компоновать так, как автор компонента не предполагал, а копию можно расширить только скопировав её снова, — именно так две копии стали тремя. Вариация была недостающим слотом, а не новым компонентом, и добавить слот дешевле, чем поддерживать форки, которые разойдутся снова при следующем изменении дизайна. Слот ещё и не пускает вызывающего во внутренности компонента: внутреннее имя класса не передаётся, поэтому разметку можно перестроить позже, не трогая ни одного места вызова.