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

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

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

<Accordion>
<Panel active>
<Title />
<Content />
</Panel> <Panel>
<Title />
<Content />
</Panel>
</Accordion>

123456789 <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 и различными именами классов, теперь у вас есть только блоки, элементы и модификаторы.

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

JavaScript. Быстрый старт

Изучите основы JavaScript на практическом примере по созданию веб-приложения

Приведенный выше псевдокод при написании в формате 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>

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

JavaScript
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>
);

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

JavaScript
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>
);

12345678910 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:

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’);
});
}
});

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

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

12345 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, вы можете получить довольно хорошие результаты:

Sass
.accordion {
&__panel {

}

&__title {

}

&__content {

}
}

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

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

JavaScript. Быстрый старт

Изучите основы JavaScript на практическом примере по созданию веб-приложения

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

Sass
.accordion {
&__panel {
&—active {
.accordion__content {
display: block;
}
}

}

&__title {

}

&__content {
display: none;

}
}

12345678910111213141516171819 .accordion {  &__panel {    &—active {      .accordion__content {        display: block;      }    }    …  }   &__title {    …  }   &__content {    display: none;    …  }}

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

Sass
@include block(‘accordion’) {
@include element(‘panel’) {
@include modifier(‘active’) {
@include element(‘content’) {
display: block;
}
}
}

@include element(‘title’) {

}

@include element(‘content’) {
display: none;

}
}

123456789101112131415161718 @include block(‘accordion’) {  @include element(‘panel’) {    @include modifier(‘active’) {      @include element(‘content’) {        display: block;      }    }  }   @include element(‘title’) {    …  }   @include element(‘content’) {    display: none;    …  }}

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

Sass
@include block(‘accordion’, (
panel: (
‘modifier(active)’: (
content: (
‘display’: block
)
)
),

title: (

),

content: (
‘display’: none,

)
));

123456789101112131415161718 @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 (по крайней мере, в последнем примере):

JavaScript
const styles = {
panel: {
‘modifier(active)’: {
content: {
‘display’: ‘block’
}
}
},

title: {

},

content: {
‘display’: ‘none’,

}
};

123456789101112131415161718 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).

JavaScript
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;

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 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

Источник webformyself.com