BEM + Vue.js — это просто
Не буду вдаваться в подробности, что за зверь такой БЭМ и на сколько он хорош или плох. Были уже сотни споров. БЭМ — это инструмент, а любым инструментом нужно уметь пользоваться, а так же понимать, зачем он был сделан. Почитать на русском о БЭМ можно на официальном сайте — 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-класса кнопки.
Шаг 2. Создание модификаторов
Пришло время создать модификаторы. Каждый модификатор представляет собой HOC, который можно создать с помощью функции withBemMod
.
Функция принимает 3 аргумента — название блока, условия применения модификатора и опционально улучшение.
Представим, что нам нужна кнопка с особыми CSS стилями. Для этого создадим модификатор.
Добавим в components/Button
папку _theme/
с файлом Button_theme_action.js
со следующим содержанием:
Первым параметром мы передаём результат работы функции cnButton()
, которую создали на первом шаге. Таким образом мы отвязываемся от текста, а если понадобится изменить название блока, то оно просто меняется в одном месте.
Вторым передаём условие — {theme: action}
. Т.е. модификатор будет применяться только тогда, когда родитель передаст свойство theme
со значением action
.
Но что делать, если нам понадобится ещё один модификатор? Всё очень просто. Мы точно так же создаём его.
Попробуем создать второй модификатор. Пусть это будет тип кнопки — ссылка.
В этом случае тэг кнопки должен меняться с button
на a
. Тут уже простым модификатором не обойтись, нужно добавлять улучшение.
Итак, начнём. Добавим в components/Button
папку _type/
с файлом Button_type_link.js
со следующим содержанием:
Первые 2 параметра уже известны. А что же за третий? А это и есть “улучшение”. Это функция, которая на вход принимает компонент и возвращает функциональный компонент, т.е. обёртку. Улучшение срабатывает только тогда, когда срабатывает условие применения модификатора.
В данном примере компонент передаёт явно свойство tag
со значением a
в оборачиваемый компонент.
Нам же нужно установить в свойство tag
значение a
. Для этого мы просто вернём контекст с новым значением свойства.
Шаг 4. Композиция
Мы создали модификаторы. Настало время вывести улучшенный компонент в нашем приложении.
Для этого мы воспользуемся функцией compose
из всё того же пакета bem-vue
.
Передаём в неё все модификаторы. От порядка аргументов зависит работа функции, поэтому убедитесь, что ваши модификаторы независимы друг от друга или передаются в нужном порядке.
На выходе получаем функцию, в которую нужно скормить базовый компонент — кнопку.
Вот теперь мы можем использовать готовый компонент кнопки в нашем приложении вместе с модификаторами:
<Button theme="action" type="link" href="#">action link Button</Button>
Всё проще чем кажется.
Заключение
После всех шагов у нас появился компонент Button
с двумя модификаторами — изменение темы и изменение типа кнопки.
Получилось немного громоздко, довольно много избыточного на первый взгляд кода. Так какие же преимущества даёт такой подход?
- Расширяемость компонентов. Мы не меняем компонент кнопки, мы дополняем его новыми модификаторами. Это очень полезно, если у вас есть большой UI-Kit, но вам постоянно надо что-то модифицировать в конкретном месте. Вы просто создаёте модификатор. Базовая кнопка как работала на одном или нескольких сайтах, так и продолжает работать.
- Модульность. Если у вас часто перебегают компоненты из одного проекта в другой, то это, возможно, ваше спасение. Вы просто переносите именно то, что нужно, оставляя неиспользуемые модификаторы за бортом. Теперь не обязательно делать супер-универсальный компонент. Достаточно оставить место для расширения.
- Чистота. Из-за того, что дополнительная логика выносится в модификаторы, компоненты будут чистыми без всяких “примесей” и легко читаемыми.
Пакет находится на стадии альфа-версии, но уже умеет многое. Он поддерживает как стандартный синтаксис компонентов, так и рендер-функции, JSX, TypeScript, а так же работает с vue-property-decorator и vue-class-component.
С Vue 3.0 пакет уменьшится в размере, а так же будет более приятный синтаксис улучшений
.
Если вы решили попробовать пакет и нашли баги или есть пожелания, не поленитесь, создайте issue или Pull Request на гитхабе.
Полезные ссылки: