От автора: BEM — это действительно отличная концепция, поскольку она решает реальную проблему эффективным образом. В его основе прекрасные идеи, но, к сожалению, он не очень элегантный. Воспринимая его, как соглашение именования классов CSS, вы можете многому научиться у BEM, как структурировать пользовательские интерфейсы, даже если вы не используете CSS для стилизации. Это то, что мне нравится в нем, так как это помогло мне сформировать образ мышления, и это может помочь сформировать путь развития в целом, а не только в отношении стилей.
Рассмотрим элемент пользовательского интерфейса. Давайте возьмем язык разметки, такой как XML / HTML / JSX. Каким было бы идеальное представление аккордеона через разметку? Это, безусловно, должно быть что-то вроде:
1 2 3 4 5 6 7 8 9 |
<Accordion> <Panel active> <Title /> <Content /> </Panel> <Panel> <Title /> <Content /> </Panel> </Accordion> |
Теперь, и без BEM, мы уже можем предложить, что в этом примере должен быть внешний родительский Block( Accordion), с дочерними Elements (Panel, Title и Content), и мы можем рассматривать свойство active как Модификатор. Это работа для BEM. Я должен иметь возможность представлять любой элемент пользовательского интерфейса таким образом. Причина, по которой BEM, как соглашение об именах CSS, работало так хорошо, кроется не в какой-то классной вещи, связанной с CSS, а в том, что он заставляет структурировать HTML разумным образом, и позволяет нам рассматривать пользовательский интерфейс в новом свете. Вместо родительских элементов div с вложенными дочерними элементами div и различными именами классов, теперь у вас есть только блоки, элементы и модификаторы.
Приведенный выше псевдокод при написании в формате HTML с использованием соглашения об именах BEM будет выглядеть примерно так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<div class="accordion"> <div class="accordion__panel accordion__panel--active"> <div class="accordion__title">Accordion Title 1</div> <div class="accordion__content"> Some content... </div> </div> <div class="accordion__panel"> <div class="accordion__title">Accordion Title 2</div> <div class="accordion__content"> Some content... </div> </div> </div> |
При правильном следовании концепции с помощью BEM очень просто создать чистое и хорошо структурированное дерево DOM. Как философия, BEM обладает таким большим потенциалом, что я не могу поверить, что он рассматривается, как соглашение об именах CSS, вместо полноценного фреймворка, или какого-то соглашения высокого уровня, сопоставимого с чем-то вроде атомарного дизайна (хотя я думаю, что они похожи).
Думая об пользовательских интерфейсах в терминах BEM, независимо от используемых технологий, мы можем разрабатывать инструменты с более дружественными API-интерфейсами, что позволяет более легко визуализировать, стилизовать и взаимодействовать с компонентами интерфейса.
Рендеринг компонентов
Ничего из этого не должно измениться, если я использую для рендеринга интерфейсов что-то вроде React. Я по прежнему должен создавать пользовательский интерфейс, основываясь на блоках, элементах и модификаторах, вместо родительских компонентов React и вложенных дочерних компонентов. В том же смысле я не должен думать при создании пользовательских интерфейсов на HTML о div, я также не должен думать о компонентах React при создании пользовательских интерфейсов в React (по крайней мере, при создании компонентов представления; к компонентам контейнера это не относится, поскольку они не визуализируют разметку).
Конечно, мы можем просто использовать React для создания BEM HTML; что-то вроде:
1 2 3 4 5 6 7 8 9 10 |
const Accordion = ({ panels }) => ( <div className='accordion'> {panels.map(({ title, content }) => ( <div className='accordion__panel'> <div className='accordion__title'>{title}</div> <div className='accordion__content'>{content}</div> </div> ))} </div> ); |
… что, действительно, хорошо, за исключением того, что это не идеально. Если мы знаем, что исходим из блоков и элементов, а не div, почему бы не стремиться к чему-то вроде:
1 2 3 4 5 6 7 8 9 10 |
const Accordion = ({ panels }) => ( <Block name='accordion'> {panels.map(({ title, content }) => ( <Element name='panel'> <Element name='title'>{title}</Element> <Element name='content'>{content}</Element> </Element> ))} </Block> ); |
Это намного более читабельно для людей (именно поэтому я думаю, что концепция BEM так хорошо работала; она помогала людям легче интерпретировать DOM).
Lucid
Lucid — это набор компонентов высшего порядка React для рендеринга элементов BEM DOM.
Приведенный выше пример является допустимым кодом при использовании Lucid.
Взаимодействие с компонентами
Без какого-либо инструмента, предоставляющего BEM-подобный API для взаимодействия с элементами DOM, чтобы заставить аккордеон из приведенного выше примера HTML работать, достаточно следующего JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// get all `accordion` blocks const accordions = document.querySelectorAll('.accordion'); accordions.forEach(accordion => { // get all `panel` elements const panels = accordion.querySelectorAll('.accordion__panel'); panels.forEach(panel => { // get `title` element const title = panel.querySelector('.accordion__title'); // toggle 'active' modifier on title click title.addEventListener('click', () => { panel.classList.toggle('accordion__panel--active'); }); } }); |
… опять же, в этом коде нет ничего действительно плохого, но, поскольку мы думаем в терминах BEM, можно переписать его примерно так:
1 2 3 4 5 |
Block('accordion').getElements('panel').forEach(panel => { panel.getElement('title').addEventListener('click', () => { panel.toggleModifier('active'); }); }); |
… что, если вы разберетесь с этим, будет делать то же самое, только с более дружественным к человеку API, который соответствует BEM.
sQuery
sQuery — библиотека для взаимодействия с элементами BEM DOM.
Приведенный выше код может быть реализован с помощью sQuery.
Стили компонентов
Используя Sass
Стилизовать BEM разметку с помощью CSS без какого-либо препроцессора так же безобразно, как использовать ее в HTML. Используя vanilla Sass, вы можете получить довольно хорошие результаты:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.accordion { &__panel { ... } &__title { ... } &__content { ... } } |
С базовыми стилями все в порядке, но когда нам нужно больше логики (например, стилизация элемента content на основе модификатора для родительского элемента panel), все может стать немного менее привлекательным:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
.accordion { &__panel { &--active { .accordion__content { display: block; } } ... } &__title { ... } &__content { display: none; ... } } |
Смысл использования & заключается в том, чтобы избежать необходимости повторять ключевые слова и поддерживать код DRY. Не вводя никаких сложных требований, нам уже пришлось нарушать это правило. Есть вещи, которые вы можете сделать, чтобы справиться с этим, но это только добавляет сложности кода в долгосрочной перспективе. Лучшим подходом может быть использование миксинов для управления требуемым поведением, в результате чего у нас будет что-то вроде API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@include block('accordion') { @include element('panel') { @include modifier('active') { @include element('content') { display: block; } } } @include element('title') { ... } @include element('content') { display: none; ... } } |
Это все еще обычный Sass, он не вводит никаких новых парадигм и соответствует BEM. Более чистым способом достичь того же можно было бы с помощью:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@include block('accordion', ( panel: ( 'modifier(active)': ( content: ( 'display': block ) ) ), title: ( ... ), content: ( 'display': none, ... ) )); |
С точки зрения Sass, следуя правилам каскадирования, это так же хорошо, как и с точки зрения DX.
Cell
Cell — это библиотека Sass для стилизации элементов BEM DOM.
Оба приведенных выше примера возможны с использованием Cell.
Использование JavaScript
Использование JavaScript для обработки стилей способом, который соответствует BEM, не слишком отличается от того, как это было сделано в Sass (по крайней мере, в последнем примере):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const styles = { panel: { 'modifier(active)': { content: { 'display': 'block' } } }, title: { ... }, content: { 'display': 'none', ... } }; |
… Вы можете увидеть, насколько это похоже на предыдущий пример Sass. Это не совпадение, просто когда вы сводите потребности к таким философиям, как BEM, то, как вы делаете что-то, в конечном итоге становится одинаковым независимо от используемых технологий. Поскольку карты Sass практически идентичны объектам JavaScript (для любых целей и задач), то, что они выглядят одинаково, на самом деле имеет смысл.
Этот объект может быть передан в функцию, которая также принимает элемент BEM DOM или NodeList элементов, и вуаля — наслаждайтесь DX…
Polymorph
Polymorph — инструмент JavaScript для стилизации элементов BEM DOM.
Polymorph — отличный инструмент для стилизации элементов DOM, который соответствуют соглашению об именах Synergy / BEM.
Заключение
Мы рассмотрели инструменты для управления рендерингом, взаимодействием и стилизацией компонентов пользовательского интерфейса, в соответствии с принципами BEM. Если учитывать все, что входит в компонент пользовательского интерфейса, это почти все. Помните, как я сказал, что не могу поверить, что парадигма BEM рассматривается просто как соглашение об именах CSS, а не как полноценный фреймворк? Вот почему я решил сделать это (и мне потребовалось всего 4 года).
Представляем Synergy
Synergy — это платформа для создания модульных, настраиваемых и масштабируемых компонентов пользовательского интерфейса для проектов React-DOM.
Synergy — это, по сути, инструментарий, включающий вещи, которые мы рассмотрели в этой статье. С помощью Synergy вы создаете модули Synergy, которые с технической точки зрения на самом деле являются просто компонентами React (созданными с помощью Lucid), которые связывают стили (используя Polymorph / sQuery). Модули Synergy предназначены для единого импорта / экспорта, и в них все уже готово, как показано на этой классной графике, которую я подготовил:
Используя Synergy, мы можем объединить все идеи, рассмотренные выше, чтобы создать функциональный, стилизованный аккордеон с нуля.
Используемое ниже соглашение идентично BEM, за исключением того, что блоки называются «модулями», а элементы называются «компонентами» (то есть соглашение об именах Synergy).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
import React, { useState } from 'react'; import '@onenexus/synergy'; const styles = { panel: panel => ([ { condition: () => panel.is('open'), styles: { heading: { 'background': '#00FFB2', 'color': '#FFFFFF' } } } ]), heading: { 'background': '#1E90FF', 'color': '#005A9C', 'padding': '1em', 'cursor': 'pointer', ':hover': { 'background': '#01BFFF', 'color': '#FFFFFF' } }, content: content => ({ 'padding': '1em', 'color': '#444444', 'display': content.parent('panel').is('open') ? 'block' : 'none' }) }; const Accordion = ({ panels }) => ( <Module name='accordion' styles={styles}> {panels.map(({ heading, content }) => { const [isOpen, toggle] = useState(false); return ( <Component name='panel' open={isOpen}> <Component name='heading' content={heading} onClick={() => toggle(!isOpen)} /> <Component name='content' content={content} /> </Component> ) })} </Module> ); export default Accordion; |
Без каких-либо других инструментов импорт этого аккордеона и его рендеринг <Accordion {…props} /> приведет к созданию аккордеона, который будет стилизованным и функциональным. Несмотря на то, что BEM является соглашением об именах CSS и технически не используется в приведенном выше примере, все же я считаю приведенный выше пример результатом философии BEM.
Автор: Edmund Reed
Источник: //itnext.io
Редакция: Команда webformyself.