Главная » Статьи » Подход к BEM как к философии пользовательского интерфейса, а не соглашению об именах CSS

Подход к BEM как к философии пользовательского интерфейса, а не соглашению об именах CSS

Подход к BEM как к философии пользовательского интерфейса, а не соглашению об именах CSS

От автора: BEM — это действительно отличная концепция, поскольку она решает реальную проблему эффективным образом. В его основе прекрасные идеи, но, к сожалению, он не очень элегантный. Воспринимая его, как соглашение именования классов CSS, вы можете многому научиться у BEM, как структурировать пользовательские интерфейсы, даже если вы не используете CSS для стилизации. Это то, что мне нравится в нем, так как это помогло мне сформировать образ мышления, и это может помочь сформировать путь развития в целом, а не только в отношении стилей.

Рассмотрим элемент пользовательского интерфейса. Давайте возьмем язык разметки, такой как XML / HTML / JSX. Каким было бы идеальное представление аккордеона через разметку? Это, безусловно, должно быть что-то вроде:

<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 будет выглядеть примерно так:

<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; что-то вроде:

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, почему бы не стремиться к чему-то вроде:

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.

Подход к BEM как к философии пользовательского интерфейса, а не соглашению об именах CSS

Приведенный выше пример является допустимым кодом при использовании Lucid.

Взаимодействие с компонентами

Без какого-либо инструмента, предоставляющего BEM-подобный API для взаимодействия с элементами DOM, чтобы заставить аккордеон из приведенного выше примера HTML работать, достаточно следующего JavaScript:

// 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, можно переписать его примерно так:

Block('accordion').getElements('panel').forEach(panel => { panel.getElement('title').addEventListener('click', () => { panel.toggleModifier('active'); });
});

… что, если вы разберетесь с этим, будет делать то же самое, только с более дружественным к человеку API, который соответствует BEM.

sQuery

sQuery — библиотека для взаимодействия с элементами BEM DOM.

Подход к BEM как к философии пользовательского интерфейса, а не соглашению об именах CSS

Приведенный выше код может быть реализован с помощью sQuery.

Стили компонентов

Используя Sass

Стилизовать BEM разметку с помощью CSS без какого-либо препроцессора так же безобразно, как использовать ее в HTML. Используя vanilla Sass, вы можете получить довольно хорошие результаты:

.accordion { &__panel { ... } &__title { ... } &__content { ... }
}

С базовыми стилями все в порядке, но когда нам нужно больше логики (например, стилизация элемента content на основе модификатора для родительского элемента panel), все может стать немного менее привлекательным:

.accordion { &__panel { &--active { .accordion__content { display: block; } } ... } &__title { ... } &__content { display: none; ... }
}

Смысл использования & заключается в том, чтобы избежать необходимости повторять ключевые слова и поддерживать код DRY. Не вводя никаких сложных требований, нам уже пришлось нарушать это правило. Есть вещи, которые вы можете сделать, чтобы справиться с этим, но это только добавляет сложности кода в долгосрочной перспективе. Лучшим подходом может быть использование миксинов для управления требуемым поведением, в результате чего у нас будет что-то вроде API:

@include block('accordion') { @include element('panel') { @include modifier('active') { @include element('content') { display: block; } } } @include element('title') { ... } @include element('content') { display: none; ... }
}

Это все еще обычный Sass, он не вводит никаких новых парадигм и соответствует BEM. Более чистым способом достичь того же можно было бы с помощью:

@include block('accordion', ( panel: ( 'modifier(active)': ( content: ( 'display': block ) ) ), title: ( ... ), content: ( 'display': none, ... )
));

С точки зрения Sass, следуя правилам каскадирования, это так же хорошо, как и с точки зрения DX.

Cell

Cell — это библиотека Sass для стилизации элементов BEM DOM.

Подход к BEM как к философии пользовательского интерфейса, а не соглашению об именах CSS

Оба приведенных выше примера возможны с использованием Cell.

Использование JavaScript

Использование JavaScript для обработки стилей способом, который соответствует BEM, не слишком отличается от того, как это было сделано в Sass (по крайней мере, в последнем примере):

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.

Подход к BEM как к философии пользовательского интерфейса, а не соглашению об именах CSS

Polymorph — отличный инструмент для стилизации элементов DOM, который соответствуют соглашению об именах Synergy / BEM.

Заключение

Мы рассмотрели инструменты для управления рендерингом, взаимодействием и стилизацией компонентов пользовательского интерфейса, в соответствии с принципами BEM. Если учитывать все, что входит в компонент пользовательского интерфейса, это почти все. Помните, как я сказал, что не могу поверить, что парадигма BEM рассматривается просто как соглашение об именах CSS, а не как полноценный фреймворк? Вот почему я решил сделать это (и мне потребовалось всего 4 года).

Представляем Synergy

Synergy — это платформа для создания модульных, настраиваемых и масштабируемых компонентов пользовательского интерфейса для проектов React-DOM.

Подход к BEM как к философии пользовательского интерфейса, а не соглашению об именах CSS

Synergy — это, по сути, инструментарий, включающий вещи, которые мы рассмотрели в этой статье. С помощью Synergy вы создаете модули Synergy, которые с технической точки зрения на самом деле являются просто компонентами React (созданными с помощью Lucid), которые связывают стили (используя Polymorph / sQuery). Модули Synergy предназначены для единого импорта / экспорта, и в них все уже готово, как показано на этой классной графике, которую я подготовил:

Подход к BEM как к философии пользовательского интерфейса, а не соглашению об именах CSS

Используя Synergy, мы можем объединить все идеи, рассмотренные выше, чтобы создать функциональный, стилизованный аккордеон с нуля.

Используемое ниже соглашение идентично BEM, за исключением того, что блоки называются «модулями», а элементы называются «компонентами» (то есть соглашение об именах Synergy).

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

Источник: https://itnext.io

Редакция: Команда webformyself.