С учетом издержек технического прогресса линия, которой следовал Фрейд, выглядит удручающе. Он согласен с комментариями Таммса:Наши изобретения — это просто усовершенствование средств, а не цель..
-- Нил Постман, Technopoly
Хотя инструменты разработки уже давно превратились из препроцессоров в стилизованные компоненты и даже в функциональный css, на мой взгляд, новые инструменты не делают наш код стилей лучше, а только быстрее — они также могут ускорить сбой кода. Вместо того, чтобы устранять основные проблемы, затрудняющие сопровождение кода, бум инструментальных средств облегчает их игнорирование. Эта статья призвана ответить на вопрос: почему код стиля сложно написать правильно и каковы его подводные камни?
Если структура чата серьезная, большинство процедур объясняются по порядку в соответствии с некоторыми важными характеристиками. Но эти так называемые важные функции на самом деле универсальны в области программирования, такие как «масштабируемость», «повторное использование», «обслуживаемость» и т. д. Согласно этому ходу мышления, пустая болтовня больше, чем применение. Таким образом, мы могли бы также посмотреть, как должен быть написан и организован код стиля, решая конкретную проблему стиля.
На рисунке ниже показан очень простой всплывающий компонент, и мы воспользуемся процессом разработки его стиля, чтобы связать воедино весь контент.
Сначала мы реализуем его простым и грубым способом.Интуитивно нам нужно всего три элемента для реализации этого всплывающего окна: div — самый внешний контейнер, h1 используется для переноса копии «Успех», а кнопка используется для реализации кнопки
<div class="popup">
<div>Success</div>
<button>OK</button>
</div>
Я не буду описывать его полный стиль, лишь несколько ключевых свойств.
.popup {
display: flex;
justify-content: space-around;
padding: 20px;
width: 200px;
height: 200px;
div {
margin: 10px;
font-size: 24px;
}
button {
background: orange;
font-size: 16px;
margin: 10px;
}
}
Реализация первой версии завершена. Вроде ничего плохого пока нет.
Проблема не в реализации, а в обслуживании. Далее я рассмотрю некоторые общие практические изменения требований, чтобы увидеть, какие проблемы существуют в приведенном выше коде.
Зависимости от элементов DOM
Теперь предположим, что вам нужно добавить элемент в разделе «Успех», чтобы отобразить конкретную информацию об успехе.
Конечно, нам нужно добавить тег div. Но если это так, стиль .popup div в приведенном выше стиле будет иметь одинаковый эффект для обоих div одновременно, а это не то, что нам нужно, очевидно, стили этих двух элементов разные. Хорошо, если вы настаиваете на использовании тегов в качестве селекторов, вы можете использовать селектор псевдокласса nth-child для различения стилей:
.popup {
div:nth-child(1) {
margin: 10px;
font-size: 24px;
}
div:nth-child(2) {
margin: 5px;
font-size: 16px;
}
Но если однажды вы решите, что «Успех» должен быть обернут в h1 вместо div, то стоимость модификации будет:
- изменить div на h1,
- Буду
div:nth-child(1)
Стиль изменен на H1, принадлежит к - Буду
div:nth-child(2)
вернуться к стилю div
Но если вы можете дать кнопке и div точное имя класса с самого начала, то при изменении элемента DOM вам нужно будет изменить только элемент DOM, не нужно изменять файл стиля.
Приведенный выше пример представляет собой случай горизонтального расширения, то есть я добавляю элемент на том же уровне, что и элемент. Та же проблема возникает и с вертикальным масштабированием, и вы вполне можете представить себе такие селекторы:
.popup div > div > h1 > span {
}
.popup {
div {
div {
span {}
}
}
}
В любом случае в приведенном выше коде, вступит ли стиль в силу или нет, сильно зависит от структуры DOM. В иерархии ряда тегов DOM, даже если есть проблема только с одним элементом (возможно, тип тега элемента был изменен или элемент был добавлен поверх него), стиль будет недопустимым в большом массиве. площадь. В то же время такой подход усложнит повторное использование стилей..popup div > div > h1 >
style, вам нужно скопировать структуру DOM туда, где вы хотите использовать ее повторно.
Итак, здесь мы можем, по крайней мере, сделать вывод: CSS не должен слишком полагаться на структуру HTML.
Причина, по которой добавлено слово «избыточный», заключается в том, что стиль вообще не может существовать независимо от структуры, например, такие зависимости, как .popup .title .icon подразумевают общий контур структуры HTML.
Итак, мы можем пойти дальше и немного подкорректировать вышеизложенный принцип: CSS должен иметь минимальные знания HTML. В идеале стиль .button должен выглядеть как одна и та же сплошная кликабельная кнопка на любом элементе, к которому он применяется.
зависимость родительского элемента
Компоненты, которые мы разработали в предыдущем разделе, обычно упоминаются в нескольких местах на странице, но всегда есть отдельные сценарии, требующие небольшого изменения компонентов для адаптации. Предположим, есть требование применить это всплывающее окно на своем мобильном веб-сайте, но для адаптации к мобильным устройствам необходимо уменьшить соответствующие размеры определенных элементов, таких как длина, ширина, внутренние и внешние поля и т. д. Как бы вы этого добиваетесь?
90% решений, которые я видел, реализуются путем добавления зависимости от родительского элемента, то есть для определения, относится ли компонент к определенному классу, и если да, то модифицировать стиль:
body.mobile {
.popup {
padding: 10px;
width: 100px;
height: 100px;
}
}
Но если на этом этапе вам нужно добавить новый стиль на планшет, я думаю, вы можете добавить еще одинbody.tablet { .popup {} }
код. И если на мобильном сайте есть два места, где нужно использовать всплывающее окно, тогда ваш код будет выглядеть так:
body.mobile {
.sidebar {
.popup
}
.content {
.popup
}
}
Такой код по-прежнему сложно использовать повторно. Если разработчику нравится стиль, открываемый попапом на мобильном сайте, и он хочет пересадить его в другое место, то мало просто внедрить компонент попапа, нужно еще найти реальный эффективный код, стиль и все уровни DOM копируются.
В случае, когда компонент уже имеет свой собственный стиль, чрезмерная зависимость от родительского компонента для косвенной настройки стиля является поведением кодирования в каждом конкретном случае, которое по существу переопределяет собственный стиль всплывающего окна. Предположим, всплывающее окно поставляется с атрибутом стиля box-shadow, но в некоторых случаях использования box-shadow может быть усугублено, а в некоторых случаях использования box-shadow может исчезнуть, тогда его собственный корень box-shadow. Это не имеет смысла вообще, потому что это никогда не вступит в силу.
Накладные расходы нарушают «принцип наименьшего удивления» и вызывают «сюрприз» у последующих сопровождающих. Если в это время будет изменен проект дизайна всплывающего окна и необходимо уменьшить тень, изменение собственного стиля не вступит в силу или не будет действовать везде. Что касается того, какие другие места не могут работать, почему они не могут работать, мейнтейнер не знает, ему также нужно проверять код на каждом конкретном случае. Это, несомненно, увеличит стоимость модификации кода.
Решение этой проблемы не так просто, как решение проблемы зависимости DOM, оно требует от нас многоаспектного подхода.
Разделение стилевых ролей
Разделение проблем всегда является проверенным методом улучшения удобства сопровождения кода. Во всех существующих методологиях организации стилей, таких как SMASS или ITCSS, правильное распределение стилей по ролям является одной из их основных идей.
В качестве примера возьмем полный стиль всплывающего окна:
.popup {
width: 100px;
height: 30px;
background: blue;
color: white;
border: 1px solid gary;
display: flex;
justify-content: center;
}
В этом наборе стилей мы видим
- Есть ширина, высота, связанные с макетом
- фон, цвет, связанный с визуальным стилем
- собственный стиль макета гибкий
- Другие стили, такие как граница
Основываясь на этих характеристиках и общих спецификациях, вы можете рассмотреть возможность разделения стилей по следующим размерам:
- Макет и размер (размер): Это нормально, когда компонент имеет разные размеры для разных родительских компонентов. Вместо определения размера, который можно переопределить в любое время, оставьте компоновку для специального компонента. И наоборот, компонент не имеет размеров сам по себе, например, он может всегда заполнять свой контейнер со 100% шириной и высотой.
На первый взгляд, такое поведение просто переносит стили (размеры) из одного компонента в другой (контейнер), но оно принципиально решает проблему зависимостей родительских элементов, о которой мы упоминали выше. Любой другой компонент, который хочет использовать всплывающее окно, не пытайтесь заботиться о том, как реализован размер всплывающего компонента, ему просто нужно закрыть себя.
На более глубоком уровне он стираетполагаться. Возможно, вы не заметили, что конфигурация стиля гибкого макета следует следующему шаблону: если вы хотите, чтобы ваши дочерние элементы были расположены в соответствии с определенными правилами, вам нужно только изменить родительский элемент и свойства стиля гибкого макета. дочерние элементы.
Другим примером анти-шаблона, который я лично считаю, являетсяtext-overflow: ellipsis
атрибут, одного атрибута стиля недостаточно для автоматического пропуска текста в контейнере, контейнер также должен соответствовать 1) ширина должна быть в px пикселях 2) элемент должен иметьoverflow:hidden
а такжеwhite-space:nowrap
Два набора стилей. То есть, когда вы хотите реализовать функцию A, вы должны полагаться на реализацию функций B и C.
Что касается того, является ли функциональный элемент макета тем же элементом, что и родительский элемент, или независимым элементом, я предпочитаю последнее, ведь несколько кодов разметки не добавят нам большой нагрузки, но четкое разделение обязанностей может принести нам будущее. техническое обслуживание. приходят много удобства
Любой стиль макета, добавленный во всплывающее окно в соответствии с этой предпосылкой, на самом деле означает, что вы добавляете неявную зависимость, потому что вы фактически подразумеваете, что его значение поля в родительском контейнере выглядит правильно.
- Модификатор: Открыто-закрыто в принципе SOLID говорит нам быть закрытыми для модификации и разработки расширений, что также верно для кода стиля.
Обычно нам нужна не просто кнопка с одним стилем, может быть, кнопка неправильного стиля с белым текстом на красном фоне и кнопка стиля предупреждения с белым текстом на желтом фоне. Обычное решение для этого варианта использования состоит не в создании N разных стилей кнопок, таких как основная кнопка, кнопка ошибки (так что должно быть много общих кодов кнопок), а на основе стиля кнопки, предоставляя стиль «Украшение» класса для достижения конечной цели. Например, базовое имя класса кнопки — это кнопка, если вы хотите, чтобы он имел стиль предупреждения, вам нужно одновременно использовать только имя класса ошибки.
<div className="button error"></div>
По сути, это тоже разделение интересов, но с этой точки зрения речь идет об «изменении» и «неизменном». Мы переместили все «переменные» в класс «украшения».
Однако это решение столкнется со многими проблемами при реализации.Первая — это дизайн класса оформления.Например, когда я определяю класс оформления, такой как ошибка, первичный и предупреждение, какие атрибуты стиля я могу переопределить, а какие нет. ?, что должно быть согласовано заранее. В противном случае, когда кто-то пишет стиль ошибки, он может бездумно перезаписывать стиль исходной кнопки до тех пор, пока он не будет выглядеть удовлетворительно. Он опирается на способность абстрагироваться, но плохую абстракцию труднее поддерживать, чем отсутствие абстракции.
- модульный: С популярной тенденцией модульности компонентов модульность стилей кажется естественной вещью. Но если смотреть в долгосрочной перспективе, модульность не ограничивается загоном стилей в угол и инкапсуляцией их для централизованного управления. Из приведенного выше примера нетрудно увидеть, что эту инкапсуляцию можно легко нарушить, заимствуя характеристики зависимости родительского элемента в стиле.
Компоненты — не единственная единица для инкапсуляции стилей.На веб-сайте также могут быть глобальные или аспектные атрибуты стиля, такие как base и reset. Мой идеальный модульный стиль должен быть в состоянии легко достичь следующего:
- Управляйте направленностью влияния стилей: например, глобальные стили могут влиять на компоненты, но компоненты не могут влиять на глобальные
- Выделение и загрязнение между модулями стилей: хотя компонент представляет собой элемент дочернего элемента B компонента, B компонента B, но не влияет на тип A
Лучшим примером этих двух моментов является общеотраслевое решение по адаптации размера шрифта при адаптивной разработке. Например, html-структура следующего компонента
<div class="ancestor">
<div class="parent">
parent
<div class="child">
hello
</div>
</div>
</div>
В стиле мы установим:
- шрифт компонента-предка изменяется относительно корневого элемента html, поэтому используйте единицы rem
- Единицы шрифта родительского и дочернего элементов должны быть изменены относительно базового шрифта компонента (также известного как предок), поэтому используйте единицы em.
.ancestor {
font-size: 1rem;
}
.parent {
font-size: 1.5em;
}
.child {
font-size: 2em;
}
Таким образом, когда нам нужно настроить размер шрифта в соответствии с устройством, нам нужно только настроить размер шрифта корневого элемента html, тогда другие элементы на странице будут настроены сами. И если мы хотим настроить только локальный стиль, нам нужно только настроить размер шрифта .ancestor, не затрагивая другие элементы.
Как вы можете видеть из прочтения этого, проблема со стилем заключается в том, что слишком легко воздействовать на другие компоненты и слишком легко подвергаться влиянию других компонентов. Проблема, с которой сталкивается большинство людей:
- Я думал, что изменяю стиль компонента A, но это незаметно повлияло на компонент B.
- На компонент А влияет несколько наборов стилей одновременно, независимо от того, кто изменяет его в одиночку, он не может достичь конечного эффекта.
Решение этой проблемы уже давно существует, и это изоляция стилей. Например, в Angular это реализовано путем добавления случайных атрибутов к элементам и прикрепления селекторов атрибутов к стилям. Например, если вы одновременно создаете компонент заголовка страницы и компонент заголовка раздела, они оба имеют стиль элемент h1, но в CSS, который вы видите после компиляции:
h1[_ngcontent-kkb-c18] {
background: yellow;
}
h1[_ngcontent-kkb-c19] {
background: blue;
}
Таким образом, все стили элементов h1 не будут зависеть друг от друга.
проблемы с реализацией
Pre-Processer
Независимо от того, насколько субъективно вы хотите избежать всех вышеперечисленных проблем, придайте своим стилям красивую чистую структуру. В процессе реализации мы все равно случайно попадем в ловушку инструмента.
Вернемся к стилю всплывающего окна, о котором мы упоминали выше:
.popup {
width: 100px;
height: 30px;
background: blue;
color: white;
}
если вы найдете{ background: blue; color: white; }
Поскольку это часто встречающийся стиль, вы хотите использовать его повторно.Исходя из предпосылки программирования с помощью Sass, очевидно, что в настоящее время у вас есть два варианта: @mixin или @extend.
Если используется миксин, код выглядит следующим образом
@mixin common {
background: blue;
color: white;
}
.popup {
@include common;
}
И если вы используете расширение:
.common {
background: blue;
color: white;
}
.popup {
@extend .common;
}
Первая проблема заключается в том, что независимо от того, какой шаблон вы выберете, трудно сказать, намеренно ли разработчик полагается на абстракцию или на реализацию. Мы можем интерпретировать @mixin common и .common как абстрактную инкапсуляцию, но очень вероятно, что последующие потребители просто захотят повторно использовать фон и цвет. Как только это будет сделано, общий модуль станет трудно изменить, потому что изменение любого свойства влияет на несколько неизвестных модулей.
В SASS, хотя мы можем добавлять параметры к имени класса и передавать их друг другу в качестве параметра, это не то же самое, что переменные и функции в нашем реальном программировании: для функций в JavaScript мы часто заботимся только о их вводе и вывод, просто определение функции не влияет на результат программы. И в тот момент, когда вы определяете класс стиля, он может уже оказывать влияние на страницу, и каждый атрибут в нем будет иметь влияние.
Если вы слышали «композиция важнее наследования», я уверен, что у вас будет более глубокий опыт в этом. Можно вспомнить побочные эффекты в системе наследования, например, наследование нарушает инкапсуляцию суперкласса, подклассы не могут сократить интерфейс суперкласса и т. д. Подобные тени можно найти в отношениях повторного использования SASS.
Расширение более опасно, чем примеси, потому что оно нарушает привычный способ организации модулей.
Например, в настоящее время существует страница page, которая имеет набор стилей заголовка страницы:
.page {
.page-title {
.icon {
width: 10px;
}
.label {
width: 100px;
}
}
}
Теперь card-title хочет повторно использовать его, расширив:
.card-title {
@extend .page-title;
}
Тогда результат после компиляции будет выглядеть очень странно:
.page .page-title .icon, .page .card-title .icon {
width: 10px;
}
.page .page-title .label, .page .card-title .label {
width: 100px;
}
Даже если вы не слышали о БЭМ, ваш опыт программирования должен подсказать вам, что стили страниц и карточек должны принадлежать разным модулям. Но на самом деле скомпилированный результат больше похож на приоритет повторного использования, принудительно связывая их вместе из поперечного сечения.
И если вы попытаетесь абстрагировать общий стиль заголовка как миксин и повторно использовать его в заголовке страницы и заголовке карточки:
@mixin title {
.icon {
width: 10px;
}
.label {
width: 100px;
}
}
.page {
.page-title {
@include title
}
}
.card-title {
@include title
}
Результат компиляции следующий:
.page .page-title .icon {
width: 10px;
}
.page .page-title .label {
width: 100px;
}
.card-title .icon {
width: 10px;
}
.card-title .label {
width: 100px;
}
Очевидно, что стили страницы и карточки более различны.
An Necessary Evil
Если бы вы спросили меня, буду ли я следовать каждому принципу, который я написал выше, мой ответ был бы нет. В реальной разработке я предпочитаю удобство в обслуживании.
Единственная константа в области программирования — это само изменение.Независимо от того, насколько точен ваш объектно-ориентированный дизайн и насколько хорошо вы разделяете компоненты перед нажатием на клавиатуру, любое бизнес-изменение может привести к тому, что все ваши проекты будут перевернуты. Следовательно, чтобы убедиться, что код точно отражает рациональность бизнес-знаний, нам необходимо время от времени переделывать дизайн кода.
Вы можете себе представить, что весь процесс требует повторного изучения архитектуры, чтения и понимания кода с нуля и проверки после модификации. Выполнение этой последовательности шагов сопряжено со значительными затратами, не считая проб и ошибок, а также упущенной возможности добавить новую функциональность из-за рефакторинга. Что еще более важно, затраты есть, а выгоды не очевидны.
Если ваш код стиля основан на дизайн-системе, то ваши затраты на изменение будут выше. Потому что для вас еще менее возможно изменить код по своему желанию с личной точки зрения, а измерить рациональность модификации языком дизайна всего продукта сверху вниз.
Другая, более практическая проблема заключается в том, что код никогда не поддерживается отдельными лицами. Когда в команде нет консенсуса по этому набору теорий или когда все понимают его только на теоретическом уровне и не заботятся о нем на практике, тщательные усилия нескольких человек в конечном итоге ни к чему не приведут. В идеале код должен максимально отказаться от «человеческого» фактора и стать промышленным продуктом на конвейере. Поэтому, когда я обнаруживаю, что фреймворк требует, чтобы люди прочитали десятки страниц документов, связанных с лучшими практиками, чтобы написать хороший код, соответствующий официальным стандартам, тогда вероятность хорошего кода в реальной работе в основном равна 0 — в выходном коде спецификации. Выше одно эффективное правило eslint сильнее десяти страниц документации. Различные принципы, описанные в этой главе, принадлежат последним.
Однако что происходит, когда код css написан в беспорядке? Продукт точно сломан, но интересные вещи по сравнению с другими ошибками:
- По сравнению со скриптами вероятность найти проблемы со стилем выше, и что видишь, то и получаешь.
- Причиненный ущерб меньше, чем функция скрипта, и продукт по-прежнему доступен в рамках проблемы.
- Стоимость устранения проблемы невелика, и ее можно быстро устранить, даже не читая полностью исходный код.
Основываясь на трех вышеприведенных пунктах и принимая во внимание сложность текущего стека технологий, высокую стоимость обучения, большой объем работы по разработке сценариев и сильное давление доставки, правильность архитектуры стиля, конечно же, та, которая была пожертвовал.
Наконец, я повторяю, что не поощряю такое поведение, это только одна из возможностей поддаться давлению реальности. Если ваш проект хорошо обеспечен ресурсами и люди полны решимости сделать все правильно, это нормально.
Functional CSS
По моему мнению, есть класс практик, которые не входят в вышеупомянутую систему, например, попутный ветер и тахион. Причина, по которой их называют «функциональными» стилями, заключается в том, что эти фреймворки не предоставляют компонентные и семантические стили, такие как .card и .btn, но предоставляют «служебные классы», такие как .overflow-auto, .box-content, они похожи на чистые функции без побочных эффектов в функциональном программировании. Когда вам нужно добавить стили к вашему элементу, просто добавьте к элементу соответствующее имя класса:
<div class="overflow-auto box-content float-left"></div>
Говорят, что эта практика выходит за рамки вышеупомянутой системы, потому что она нарушает предпосылку, о которой я говорил выше: существует зависимость между стилями и структурой DOM. В этом режиме программирования, поскольку больше нет «каскадной» связи, стили каждого элемента независимы и не влияют друг на друга.
Таким образом, этот шаблон подобен раю, и можно избежать всех проблем, упомянутых в этой статье: зависимостей родительских элементов, связывания ролей, запутанного повторного использования в препроцессорах.
Но подумайте, очень ли этот способ похож на встроенный стиль? Использование встроенного стиля также может решить все проблемы, упомянутые выше. Мы вернулись к тому, с чего начали?
Причина, по которой я не даю никаких дальнейших рекомендаций или возражений, помимо поставленного выше вопроса, заключается в том, что, с одной стороны, такая практика существует.очень большойизспор. С другой стороны, мне не хватает опыта работы с такими фреймворками. Критерием оценки опыта здесь является не «использован ли он», а «вложен ли он в масштабный проект, в котором много людей работают вместе в течение длительного времени» — ключевые слова «долгосрочный», «мульти -человек», и «крупномасштабный» очень важны. Потому что, когда мы выбираем технологию, мы должны учитывать соответствие существующим проектам, стоимость адаптации команды и оценивать, смогут ли огромные преимущества, которые она может принести нам в долгосрочной перспективе, компенсировать затраты на ее замену. Этих переживаний мне не хватает.
Эта статья была также опубликована одновременно вАвтостопом по передовым технологиям, добро пожаловать, чтобы следовать