Борючись з БЕМ 10 основних помилок та як їх уникнути

Зараз у мене немає жодних сумнівів щодо способу називати речі. Єдине, що відштовхувало мене протягом тривалого часу, це потворний синтаксис. Дизайнер усередині мене не хотів захаращувати мою шикарну розмітку жахливими подвійними підкресленнями та дефісами.

Розробник у мені дивився на це прагматично. І, в кінцевому рахунку, модульний спосіб побудови інтерфейсу користувача переважив праву сторону мого мозку, яка стверджувала: "Але він недостатньо хороший!". У таких випадках, я віддаю перевагу функціональності над зовнішньою формою. У будь-якому разі вистачить порожніх розмов. Представляю вашій увазі 10 проблем, з якими я поборовся і кілька порад, як їх вирішити.

1. "Що робити з "онученими" селекторами (і не тільки)?"

Для ясності внучатий селектор використовується, коли вам потрібно звернутися до елемента, вкладеного на два рівні. Ці погані хлопчики — прокляття мого життя, і я впевнений, що це одна з причин, через яку люди мають негайну відразу до БЕМ. Наведу приклад:

Як ви могли помітити, імена можуть швидко вийти з-під контролю і чим більше вкладеності, тим потворнішим стає ім'я класу елемента. Я використовував короткий блок з назвою c-card і короткі імена body, text, link, але спробуйте уявити, що вийде, якщо початковий блоковий елемент буде названий c-drop-down-menu.

Я вважаю, що подвійне нижнє підкреслення має з'являтися лише одного разу на ім'я селектора. БЕМ розшифровується як Блок_Елемент - Модифікатор , а не Блок Елемент Елемент - Модифікатор . Також уникайте багаторівневого найменування елементів. Якщо у вас виходять рівні пра-пра-пра-правнуків, то, можливо, вам варто переглянути структурукомпонентів.

Найменування БЕМ не прив'язано строго до DOM, так що не має значення на скільки рівня знаходиться вкладений елемент. Угода про імена в першу чергу допомагає вам побачити стосунки з блоком на верхньому рівні – у нашому випадку c-card .

Як би я розглядав той самий картковий компонент:

Це означає, що всі вкладені елементи торкнуться лише карткового блоку. Також, ми могли б перемістити текст і зображення в c-card__header або навіть внести новий елемент c-card__footer при цьому не ламаючи семантичної структури.

2. "Які назви використовувати?"

До цього часу ви, можливо, помітили використання c-в моїх прикладах. Цей префікс розшифровується як "компонент" і лежить в основі всіх імен моїх БЕМ класів. Ідея запозичена з техніки найменувань Гаррі Роберта, яка покращує читання коду.

Система, яку я прийняв, і безліч префіксів, які будуть зустрічатися в прикладах цієї статті:

Я виявив, що подібні імена неймовірно покращили читання мого коду. Навіть якщо у мене не вдасться підсадити вас на БЕМ, то цей прийом безперечно варто запам'ятати.

Ви могли б також взяти й інші префікси, такі як qa- для тестів на якість (quality-assurance), ss- для різних хуків на стороні сервера (server-side) і т.д. Але список вищий — вже добрий початок, і ввести нові імена ви можете після того, як опануєте цю техніку.

Ви побачите гарний приклад використання цього стилю найменувань у наступній проблемі.

3. "Як мені називати враппери (wrappers)?"

Деякі компоненти вимагають батьківського враппера (або контейнера), який ставить макет дочірнім елементам. У цих випадках я завжди намагаюся абстрагувати макет в модуль макету,такий як l-grid та додавати кожен компонент як вміст l-grid__item .

У нашому картковому прикладі, якби ми хотіли розмістити список із чотирьох c-cardів, я використав би наступну розмітку:

Тепер у вас має бути тверде уявлення про те, як розташування модулів та імена компонентів мають поєднуватися разом.

Не бійтеся використовувати трохи більш розвиненої розмітки, щоб потім у вас не було головного болю. Нікому не стане краще від того, що ви скоротите пару

У деяких ситуаціях це не можливо. Якщо, наприклад, ваша сітка не збирається вас слухатися, або ви просто хочете якесь ім'я із змістом для вашого батьківського елемента, то що вам робити? Я, як правило, вибираю слово container або list, залежно від ситуації. Застосовуючи це до нашого карткового прикладу, я міг би використовувати

4. "Кроскомпонентні… Компоненти?"

Ще одна проблема, що часто зустрічається, — компоненти, чиї стилі або позиціонування зачіпаються батьківським контейнером. Різні рішення цієї проблеми детально описані у Simurai. Я вам розповім лише про той підхід, який я вважаю найефективнішим.

Коротко, давайте припустимо, що ми хочемо додати c-button до нашого card__body з попереднього прикладу. Ця кнопка вже є його компонентом та оформлена так:

Якщо у вас немає жодних відмінностей між звичайним компонентом кнопки, тоді проблем немає, ми просто перемістимо її ось так:

Однак, що відбувається, коли є невеликі відмінності в стилях - наприклад, ми хочемо її трохи зменшити, закруглити кути, але тільки тоді, коли вона є частиною компонента c-card ?

Кросскомпонентний клас здався мені найнадійнішим рішенням:

У прикладі вище клас c-card__c-button намагається змінити однеабо кілька існуючих властивостей c-button , але залежить від джерела виклику (або навіть специфіки), для успішного застосування. Клас c-card__c-button буде працювати тільки в тому випадку, якщо він визначений після блоку c-button , який може швидко вийти з-під контролю, якщо ви будуватимете більше таких кроскомпонентів. (Покладатися на !important, звичайно, можна, але я б не став!).

Оформлення по-справжньому модульного UI елемента має бути повністю незалежно від батьківського контейнера — воно має виглядати однаково незалежно від того, де ви його розташуєте. Додавання класу з іншого компонента для завдання стилю, як це робить спосіб з "міксами", порушує відкриті/закриті принципи компонентно-орієнтованого дизайну, тобто не повинно бути ніякої залежності від іншого модуля для естетики.

Найкраще це використовувати модифікатор для цих невеликих косметичних відмінностей, тому що ви легко зможете використовувати їх знову в міру зростання вашого проекту.

Навіть якщо ви не використовуєте ці додаткові класи знову, принаймні ви не будете прив'язані до батьківського контейнера або до початкового порядку для застосування змін.

Інший спосіб - піти до вашого дизайнера і сказати йому, що кнопки повинні бути сумісні з іншими, і уникати цієї проблеми взагалі.

5. "Модифікатор чи новий компонент?"

Одна з найбільших проблем вирішити, де компонент закінчується, і починається новий. У прикладі з c-card ви могли б створити інший компонент, названий c-panel з дуже схожими атрибутами стилів, але з дуже помітними відмінностями.

Але що визначає необхідність використання двох компонентів, c-panel і c-card або простого модифікатора для c-card , який застосовує унікальні стилі.

Можна, можливолегко переповнити проект модулями та зробити все у вигляді компонентів. Я рекомендую почати з модифікаторів, якщо вам здасться, що керування будь-яким компонентним CSS файлом стає занадто складним, то, можливо, настав час розділити деякі модифікатори. Хороший показник, коли вам доводиться скидати все біля блоку CSS для стилізації нового модифікатора - це, як на мене, час для створення нового компонента.

Найкраще, якщо ви працюєте в команді інших розробників або дизайнерів, дізнатися їхню думку. Зберіть їх на кілька хвилин і обговоріть їх. Незважаючи на те, що ця відповідь швидше виглядає як відмазка, для великих додатків дуже важливо розуміти, які модулі доступні і що є компонентом.

6. "Як керувати станами?"

Це досить поширена проблема, особливо коли ви стилізуєте компонент в активному або відкритому стані. Давайте скажемо, що наші картки перебувають у активному стані; так ось, коли ми натискаємо на них, вони виділяються гарним обведенням. Що щодо назви цього класу?

У вас два варіанти: або використовувати хук автономного стану, або використовувати БЕМ-подібну назву модифікатора на рівні компонента:

Має сенс дотримуватись стандартного набору хуків стану. Кріс Пірс зібрав хороший список, який я рекомендую подивитися.

7. "Коли краще не додавати новий клас до елемента"

Я можу зрозуміти людей, які перевантажені величезною кількістю класів, необхідних для побудови складної частини інтерфейсу користувача, особливо, якщо вони не присвоювали клас кожному тегу.

Зазвичай я прикріплюю класи до всього, що має бути стилізовано по-різному в контексті компонента. Я часто залишаю теги p без класу, якщо компонент не вимагає,щоб вони виглядали особливо в цьому контексті.

Згоден, це позначатиме те, що ваша розмітка міститиме безліч класів. Але, зрештою, ваші компоненти будуть здатні жити незалежно і зможуть переміщатися без побічних ефектів.

Зважаючи на глобальний характер CSS, присвоєння класу до всього дає нам повний контроль над рендером. Початковий дискомфорт вартий переваг повністю модульної системи.

8. "Як вкладати компоненти?"

Допустимо. що ми хочемо відобразити чек-лист у нашому компоненті c-card. Приклад того, як не варто це робити:

У нас є кілька проблем. Перша – дворівневий селектор, про який ми дізналися у першому розділі. Друге - всі стилі, застосовані до c-card__checklist__item, відносяться тільки до цього конкретного випадку, через що їх не можна використовувати знову.

Я волію розбити список на модуль розмітки, предмети чек-листа на власні компоненти, що дозволяє використовувати їх самостійно іншому місці. Це дозволяє повернутися нашому префіксу l-до гри:

Це рятує вас від необхідності повторювати ці стилі, а також означає, що ми можемо використовувати l-list та c-checkbox в інших місцях нашої програми. Це вимагає трохи більше розмітки, але натомість ми отримуємо читання, інкапсуляцію та можливість повторного використання. Можливо, ви помітили, що це основні теми!

9. "Чи не з'являться у компонентів мільйон класів?"

Деякі вважають, що безліч класів на елемент - погано, і, також, можуть додатися модифікатори. Особисто мені це не здається проблемою, тому що це робить код більш читаним і я точно знаю, що він повинен робити.

Приклад із чотирьох класів, за допомогою яких стилізується кнопка:

Я розумію, що цейсинтаксис трохи потворний, але зрозумілий.

Однак, якщо від цього у вас починає боліти голова, ви можете поглянути на техніку Сергія Зароускі. Простіше кажучи, нам потрібно використати . ] у таблиці стилів для імітації додаткової функціональності. Якщо цей синтаксис здається вам знайомим, це тому, що Icomoon використовує схожий спосіб керувати селекторами іконок.

З цією технікою, ваш код може виглядати приблизно так:

Я не знаю, чи є зниження продуктивності при використанні > та > більше, ніж під час використання індивідуальних класів, але теоретично, це класна альтернатива. Для себе мені вистачає варіанта з кількома класами, але мені здається, що цей спосіб безперечно заслуговує на згадку для тих, хто віддає перевагу альтернативі.

10. "Чи можемо змінити реакцію типу компонента?"

Цю проблему поставив мені Arie Thulank і до якої я намагався знайти 100% рішення.

Прикладом може бути меню, що випадає, яке перетворюється на набір вкладок в заданий момент, або закадрова бічна навігація, яка перетворюється на панель меню.

По суті, один компонент може мати два різні стани, продиктовані медіа-запитом.

Гаррі Робертс згадував про чуйні префікси, один із способів керувати цим. Його підхід призначений більше для змін у макеті та написання стилів, а не зрушуючи всі компоненти, але не бачу причин, через які ми не можемо застосувати цю техніку тут. Таким чином, ви можете назвати класи так:

Надалі це житиме у ваших медіа-запитах для відповідних розмірів екрану. Рада: Вам доведеться відключати @ за допомогою зворотної косої межі, як тут:

У мене не було причин для створення таких компонентів, але цей спосіб виглядає дуже доброзичливимпо відношенню до розробника. Іншій людині має бути досить просто зрозуміти ваші наміри. Але я не пропагую такі імена, як small-screen та large-screen — вони використані лише для покращення читаності.

Висновок

Хардкорна конфа за С++. Ми запрошуємо лише профі.