В этом посте я хочу ответить на два вопроса:

  1. Что такое БЭМ?
  2. Как БЭМ применять к стилизации компонентов React?

БЭМ - это аббревиатура от Block Element Modifier. Это не фреймворк или библиотека. Это всего лишь небольшой кусочек большой головоломки архитектуры CSS. В частности, БЭМ - это строгое соглашение об именах селекторов CSS, которое обеспечивает ценную мысленную модель для составления компонентов. Позвольте мне объяснить…

Термин элемент в БЭМ не следует путать с элементом HTML . Элемент HTML - это фактическое применение тега HTML в документе HTML. Например, тег input, представленный как элемент HTML, - это <input />. Элементы HTML поддерживают такие атрибуты, как атрибут class (например, <input class="fancy-input" />), которые мы будем активно использовать при исследовании БЭМ.

При создании пользовательских интерфейсов сегодня мы часто используем библиотеки JavaScript на основе компонентов, такие как React, Vue, Svelte и т. Д. У разных библиотек и фреймворков есть свои мнения о стилях. React, к лучшему или худшему, по большей части не имеет никакого мнения. Еще до того, как я узнал, что такое React, я использовал БЭМ для создания библиотек компонентов. Забегая вперед на несколько лет, я нахожусь на коленях в мире React и до сих пор считаю БЭМ своим помощником в создании масштабируемого и предсказуемого CSS.

Блок БЭМ

B для блока. Блоки - это отдельные компоненты, которые можно или нельзя повторно использовать на вашем сайте. В контексте React блок обычно отображается в один презентационный компонент React. Это могут быть большие содержательные компоненты, такие как герой страницы, заполненный ярким изображением, заголовок, призыв к действию и меню навигации. Блоки также имеют форму простых компонентов, таких как кнопки, ссылки или элементы заголовка. БЭМ-блоки могут и часто должны содержать другие БЭМ-блоки. Если мы определили компоненты блока Button, Navigation и Heading, очевидно, что наш Hero блок, вероятно, будет содержать такие компоненты.

При стилизации блока идея состоит в том, что блок должен содержать все базовые стили, необходимые для этого компонента. Варианты блока, называемые модификаторами, будут иметь правила CSS, которые изменяют стили базовых блоков (мы коснемся этого позже).

Наш Button компонент может выглядеть так:

Я предпочитаю называть селектор блока таким же, как и компонент. В этом случае Button отображается на .button имя класса. Некоторым нравится использовать название блока с заглавной буквы (.Button). Лично я использую kebab для имен моих классов CSS, но вы можете сделать это сами 😁.

Модификатор БЭМ

Пропуская на мгновение БЭМ-элемент, я хочу объяснить модификатор БЭМ. Модификаторы - это именно такие - модификаторы. Они предназначены для изменения базового класса (обычно блока). Модификатор должен включать только те стили, которые необходимы для изменения базового класса. В модификаторах используется двойной дефис --, чтобы отделить имя модификатора от имени базового класса, которое они изменяют:

В приведенном выше фрагменте кода мы ввели 3 новых модификатора: контур, основной и контур-основной. Эти модификаторы предназначены для иллюстративных целей, но они подчеркивают два важных момента, которые я хочу сказать о модификаторах:

  1. Модификаторы следует объявлять после объекта, который они изменяют. Это необходимо, потому что специфика CSS для .button и всех его модификаторов одинакова. Однако, поскольку модификаторы появляются после базового класса, правила модификатора имеют приоритет перед конфликтующими правилами, определенными в базовом классе.
  2. Модификаторы должны включать только те правила, которые необходимо изменить. Например, модификатор структуры имеет только 3 правила: background-color, border-color и color. Он не включает ничего о font-size, font-weight и т. Д. Это осталось для определения базового класса .button. Это означает, что при применении модификатора к элементу HTML вы также должны включить базовый класс.

Альтернативный, опрометчивый подход

Вы можете посмотреть на приведенный выше пример и найти избыточное пространство имен БЭМ-блоков неудобным и удивиться, почему бы вам не сделать что-то вроде этого:

Применительно к HTML он изменится на следующее:

С другой стороны, этот подход означает, что писать меньше, и мы эффективно поместили наши модификаторы в пространство имен базового класса, связав селекторы (например, .button.outline). Причина, по которой я не рекомендую этот подход, говорит о моем более широком, но связанном с этим мнении об архитектуре CSS: Селекторы CSS должны быть как можно менее конкретными.

Проблема с примером .button.outline {...} заключается в том, что мы удвоили специфичность селектора. Это может создать проблемы, если я позже приду и захочу дополнительно изменить color, background-color или border-color для конкретного случая использования. Чтобы мои новые стили работали, мой новый селектор должен быть по крайней мере таким же конкретным, как .button.outline.

В этом случае цвет границы .flashy-button будет немедленно заменен более конкретным селектором .button.outline. Чтобы исправить это, нам нужно будет ввести любое количество хаков, например, удвоить имя класса (.flashy-button.flashy-button {...}) или добавить туда !important. Мы можем избежать этой проблемы, сохранив относительно плоский график специфичности CSS, используя одно имя класса для модификаторов.

Применение модификаторов к React

Я большой поклонник библиотеки classnames. Это полезный способ условного применения имен классов к элементам React. Давайте добавим поддержку трех наших модификаторов в наш Button компонент:

Библиотека classnames может принимать в качестве аргументов любое количество строк или объектов. Строковые аргументы объединяются в результирующую строку, возвращаемую функцией classNames. Объекты используются для условного применения имен классов. Ключ объекта представляет имя класса, которое вы можете добавить. Соответствующее значение - это выражение, которое принимает значение true или false. Если выражение истинно, имя класса, хранящееся в ключе объекта, применяется к результирующей строке, возвращаемой classNames. Если бы мы визуализировали наш Button компонент со следующими реквизитами:

Функция classNames вернет 'button button--outline-primary'.

БЭМ-элемент

По моему опыту, БЭМ-элементы - это наиболее неправильно понимаемая часть БЭМ-уравнения. БЭМ-элементы обозначаются знаком __ (двойное подчеркивание). Структура соответствует ..<block-name>__<child-name>. БЭМ-элементы имеют пространство имен их родительского блока. Важна тесная взаимосвязь между БЭМ-блоком и БЭМ-элементом. Однако БЭМ-элемент следует использовать только в том случае, если стилизация HTML-элемента только имеет смысл в контексте оборачивающего БЭМ-блока. Чтобы проиллюстрировать это, я воспользуюсь более сложным примером БЭМ-блока: героем. Наш компонент React может начинаться так:

В данном случае БЭМ-блок - это hero, а метко названное имя класса hero правильно применяется к элементу верхнего уровня: header. Но как мне стилизовать дочерние элементы: h1, p и Button? Должен ли я немедленно создать .hero__title, .hero__message и .hero__button? Может быть. Но не обязательно.

Начнем с h1 и p. Независимо от всего этого разговора о БЭМ, разумно предоставить некоторые базовые стили для общих элементов, таких как заголовки, абзацы и т. Д. В качестве примера я бы включил это перед любым из моих стилей компонентов.

В контексте блока героя, если стили базовой линии, примененные к этим элементам, подходят, то вы можете оставить их в покое. То же самое и с нашим пользовательским Button компонентом. Если он выглядит так, как вы хотите, то он будет оформлен с использованием собственного имени класса БЭМ-блока .button, и мы сможем назвать это днем.

Однако я часто вижу, что разработчики сразу же переходят к стилизации всех элементов-предков блоков как БЭМ-элементов. Вместо этого они делают следующее:

В худшем случае разработчик может полностью проигнорировать наш многоразовый Button и создать чистый набор стилей для .hero__button, который дублирует многие стили в блоке .button. Это лишает смысла многоразовые компоненты, поэтому не делайте этого.

Итак, опять же, если все выглядит хорошо и дальнейшее оформление не требуется, мы можем оставить наш блок героя в покое, не создавая никаких стилей БЭМ-элементов. Однако, если нам нужно настроить несколько вещей, специфичных для нашего сценария использования героя, то самое время ввести элементы БЭМ:

Интересно то, что Button служит одновременно и блоком (.button), и БЭМ элементом (.hero__cta). Совершенно нормально! Насколько я понимаю, это наилучшая композиция CSS 🤓

У нашего компонента Button есть одна крошечная складка. Компонент Hero пытается передать пользовательское свойство className нашему Button. Если мы используем существующую реализацию Button, Button проигнорирует это свойство. Давайте исправим это:

Мы должны разрушить опору className из объекта props, а затем добавить его к аргументам classNames(). Если значение передается в опору className, оно будет соответствующим образом применено к результирующей переменной classes. Если он не указан, функция classNames будет опускать значение undefined из результата.

Замечание о специфике CSS

Если вы посмотрите на наш главный пример, где мы представили несколько стилей БЭМ-элементов, вы заметите, что БЭМ-элемент hero__cta был применен к Button элементу. Мы знаем, что под капотом Button затем применит имена классов button и hero__cta к визуализированному <button />. .button и .hero__cta имеют одинаковый уровень специфичности. Наше намерение состоит в том, чтобы цвет границы, применяемый к Button, был цветом, указанным в .hero__cta, а не базовым значением, определенным в .button. Как сделать так, чтобы это произошло?

Здесь на помощь приходит ваш комплектовщик. Обратите внимание на порядок, в котором мы загружаем зависимости ./Button и ./Hero.css в модуль Hero.js:

./Hero.css импортируется после ./Button. Это важно. Когда ваш сборщик (например, Webpack) создает окончательные пакеты (как JavaScript, так и CSS), он делает это, уделяя пристальное внимание порядку, в котором создаются зависимости. Поскольку ./Button импортируется до ./Hero.css, Webpack (и, предположительно, другие сборщики) войдут в Button.js первым, прежде чем даже обращать внимание на ./Hero.css. А поскольку Button.js импортирует ./Button.css, Button.css будет включен в результирующий пакет CSS перед содержимым Hero.css. Вот почему я всегда рекомендую вам загрузить зависимость CSS вашего компонента в качестве последней зависимости в вашем модуле.

В заключение

Сегодня существует множество способов создать CSS. Существует ряд вариантов CSS-in-JS, модулей CSS, а также более «традиционных», подобных тем, о которых я говорил в этой статье. На мой взгляд, CSS-in-JS лучше всего подходит в тех случаях, когда стили очень динамичны на основе данных (реквизита). Но в тех случаях, когда все, что вам нужно, - это несколько простых вариантов и разумный способ моделирования и масштабирования CSS вашего сайта / приложения, БЭМ оказалась потрясающей масштабируемой моделью.

Последнее замечание: я также большой поклонник модулей CSS. В следующей статье я расскажу о том, как применить методологию БЭМ к модулям CSS.