BEM + Vue.js — это просто

https://ru.bem.info/methodology/

Не буду вдаваться в подробности, что за зверь такой БЭМ и на сколько он хорош или плох. Были уже сотни споров. БЭМ — это инструмент, а любым инструментом нужно уметь пользоваться, а так же понимать, зачем он был сделан. Почитать на русском о БЭМ можно на официальном сайте — https://ru.bem.info/methodology/.


Обычно, БЭМ используют для разбиения вёрстки на блоки, элементы и модификаторы, а так же для именования CSS-классов.

Но БЭМ на самом деле о большем. У авторов БЭМ есть свои инструменты для работы, которые почти нереально использовать с современными js-фреймворками.

А что, если взять модульность БЭМ и попробовать прикрутить её к Vue.js? В раздумьях родился пакет bem-vue. На самом деле я не придумывал пакет с нуля, а портировал @bem-react/core.

Что это даёт? Мы начинаем действительно мыслить в терминах БЭМ. Это касается не только CSS-классов, но и компонентов.


Основные концепции

  • Модификатор — это HOC над основным компонентом (блоком или элементом).
  • Возможность совмещать бесконечное количество модификаторов для каждого компонента путём их композиции.
  • Автоматическое именование CSS-классов.
  • Компоненты vue.js работают стандартно. Можно менять уже существующие.
  • Неиспользуемые модификаторы не попадают в сборку.

Итак, давайте попробуем что-нибудь сделать с помощью пакета.
Представим, что у нашего приложения уже имеется такая структура:

Начальная структура приложения

Шаг 0. Установка зависимостей

Сперва добавим пакет к себе в приложение:

npm i bem-vue --save

Шаг 1. Создание основного компонента

Определим пространство Button. Для этого добавим в папку components/Button файл index.js. В нём будет инициализация функции, которая поможет строить CSS-классы автоматически:

import { cn } from 'bem-vue';

export const cnButton = cn('Button');

Теперь мы можем использовать полученную функцию cnButton для построения CSS-классов блока и его элементов.

На самом деле cn — это функция из уже готового пакета @bem-react/classname. Все функции этого пакета проксируются в bem-vue. Рекомендую после статьи ознакомиться с его возможностями. Это позволит лучше понимать работу генерации CSS-классов.

Добавим в компонент кнопки автоматическое построение CSS-класса кнопки.

Код компонента Button

Шаг 2. Создание модификаторов

Пришло время создать модификаторы. Каждый модификатор представляет собой HOC, который можно создать с помощью функции withBemMod.
Функция принимает 3 аргумента — название блока, условия применения модификатора и опционально улучшение.

Представим, что нам нужна кнопка с особыми CSS стилями. Для этого создадим модификатор.
Добавим в components/Button папку _theme/ с файлом Button_theme_action.js со следующим содержанием:

Модификатор theme со значением action

Первым параметром мы передаём результат работы функции cnButton(), которую создали на первом шаге. Таким образом мы отвязываемся от текста, а если понадобится изменить название блока, то оно просто меняется в одном месте.

Вторым передаём условие — {theme: action}. Т.е. модификатор будет применяться только тогда, когда родитель передаст свойство theme со значением action.

Но что делать, если нам понадобится ещё один модификатор? Всё очень просто. Мы точно так же создаём его.

Попробуем создать второй модификатор. Пусть это будет тип кнопки — ссылка.

В этом случае тэг кнопки должен меняться с button на a. Тут уже простым модификатором не обойтись, нужно добавлять улучшение.

Итак, начнём. Добавим в components/Button папку _type/ с файлом Button_type_link.js со следующим содержанием:

Модификатор type со значением link

Первые 2 параметра уже известны. А что же за третий? А это и есть “улучшение”. Это функция, которая на вход принимает компонент и возвращает функциональный компонент, т.е. обёртку. Улучшение срабатывает только тогда, когда срабатывает условие применения модификатора.
В данном примере компонент передаёт явно свойство tag со значением a в оборачиваемый компонент.

Нам же нужно установить в свойство tag значение a. Для этого мы просто вернём контекст с новым значением свойства.

Шаг 4. Композиция

Мы создали модификаторы. Настало время вывести улучшенный компонент в нашем приложении.

Для этого мы воспользуемся функцией compose из всё того же пакета bem-vue.

Передаём в неё все модификаторы. От порядка аргументов зависит работа функции, поэтому убедитесь, что ваши модификаторы независимы друг от друга или передаются в нужном порядке.

На выходе получаем функцию, в которую нужно скормить базовый компонент — кнопку.

Вот теперь мы можем использовать готовый компонент кнопки в нашем приложении вместе с модификаторами:

<Button theme="action" type="link" href="#">action link Button</Button>

Всё проще чем кажется.

Итоговый пример

Заключение

После всех шагов у нас появился компонент Button с двумя модификаторами — изменение темы и изменение типа кнопки.

Получилось немного громоздко, довольно много избыточного на первый взгляд кода. Так какие же преимущества даёт такой подход?

  1. Расширяемость компонентов. Мы не меняем компонент кнопки, мы дополняем его новыми модификаторами. Это очень полезно, если у вас есть большой UI-Kit, но вам постоянно надо что-то модифицировать в конкретном месте. Вы просто создаёте модификатор. Базовая кнопка как работала на одном или нескольких сайтах, так и продолжает работать.
  2. Модульность. Если у вас часто перебегают компоненты из одного проекта в другой, то это, возможно, ваше спасение. Вы просто переносите именно то, что нужно, оставляя неиспользуемые модификаторы за бортом. Теперь не обязательно делать супер-универсальный компонент. Достаточно оставить место для расширения.
  3. Чистота. Из-за того, что дополнительная логика выносится в модификаторы, компоненты будут чистыми без всяких “примесей” и легко читаемыми.

Пакет находится на стадии альфа-версии, но уже умеет многое. Он поддерживает как стандартный синтаксис компонентов, так и рендер-функции, JSX, TypeScript, а так же работает с vue-property-decorator и vue-class-component.

С Vue 3.0 пакет уменьшится в размере, а так же будет более приятный синтаксис улучшений.

Если вы решили попробовать пакет и нашли баги или есть пожелания, не поленитесь, создайте issue или Pull Request на гитхабе.

Полезные ссылки: