Для описания CSS в веб-приложениях мы используем препроцессор Stylus в сочетании с библиотекой nib. Также используется Autoprefixer, так что писать вручную browser-specific префиксы не нужно.
В MDN (Mozilla Developer Network) есть замечательная статья о том, как писать эффективный CSS — Writing efficient CSS — которая настоятельно рекомендуется к прочтению. Если коротко, то можно выделять 2 главных правила:
>
), вместо
этого следуеть испольльзовать наследование свойств CSS от родительского элемента к дочерним.Этими правилами мы будем руководствоваться для выработки принципов правильной организации CSS.
h1
, h2
,...),>
).#
) вообще не следует использовать.style
сделует применять только в случае динамического
характера значений свойств.У нас используется css-modules, для того, чтобы избегать конфликты имён CSS-классов, объявленных в разных классах, и случайных переопределений стилей.
В связи с этим, для локальных CSS-классов компонентов следует выбирать лаконичные понятные имена (например, title
,
add-button
, footer
) без использования каких-либо префиксов, чтобы обеспечить их уникальность. Добавление
префиксов, обеспечивающих глобальную уникальность имени, происходит на этапе сборки проекта автоматически.
Однако, при выборе имён следует учитывать, что в рамках данного компонента могут быть использованы имена из внутренних или внешних библиотек классов. Если имя локального класса совпадёт с именем класса из дополнительно подключённой библиотеки, то это затруднит использование класса с тем же именем из внешней библиотеки:
classNames
(cx1
). Впрочем, классы
внутренних библиотек должны содержать префикс lc-
и существует риск конфликта имён между двумя библиотеками.classnames
или
посредством отдельного unbinded инстанса classnames
.Объявляет набор классов, которые используются только в данном компоненте.
Располагается в папке компонента (для компонентов-пакетов) или в папке style/
рядом с файлом компонента,
называется так же, как и компонент, но со строчной буквы и с расширением .styl
.
Должен подключаться явно в файле компонента:
Для использования в компоненте библиотеку необходимо явно подключить в основном файле компонента:
1 2 3 4 5 | import classNames from 'classnames/bind'
const cx = classNames.bind(require('./style/someComponent.styl'))
const className = cx('item-label')
|
Могут понадобится, если основной файл разросся и удобно выделить какие-то куски в отдельные файлы.
Располагаются рядом с основным stylus-файлом компонента.
Могут подключаться двумя способами:
С помощью директивы @require
в основном stylus-файле компонента. Это удобно, если дополнительный файл понадобился
только для того, чтобы разбить большой файл.
Прямым подключением с помощью classNames.bind
наряду с осноным stylus-файлом в файле компонента. Это удобно, если
дополнительный файл является мини-библиотекой и используется в нескольких соседних компонентах.
1 2 3 4 5 | const cx = classNames.bind(Object.assign(
{},
require('./style/someComponent.styl')
require('./style/addForm.styl'),
))
|
Помогают убирать дублирование кода. Они не содержат конечных классов, но определяют переменные и базовые наборы свойств, используемые в нескольких других местах (общих библиотеках классов и стилях компонентов).
Располагается в папке style
любого модуля приложения в зависимости от области применимости.
Подключаются в с помощью директивы @require
в stylus-файлы внутренних библиотек или компонентов:
1 2 3 4 5 6 7 8 9 10 | @require 'web/master/style/webMasterVars.styl'
@require 'publicVars.styl' // web/master/public/components/style/publicVars.styl
.some-element
font-size: primaryFontSize
line-height: publicTitleHeight
.title
@extends $title
font-weight: bold
|
Можно подключать файлы либо относительно текущей папки (без использования ./
), либо любой файл относительно папки
src/
(без использования начального /
).
Предупреждение
Обратите внимание, что файлы, подключаемые с помощью директивы @require
в другие stylus-файлы, не должны
содержать в себе объявления конечных css-классов, иначе это приведёт к дублированию этих классов в каждом файле,
куда была подключена эта библиотека.
Файлы, содержащие объявления конечных css-классов, должны подключаться только напрямую в компонентах, где эти классы будут использоваться.
Наборы классов для общих базовых элементов и лейаута, форматирования контента и т.п., например как должны выглядеть кнопочки, параграфы текста, инпуты и т.д.
Следует располагать в папке style
одного из корневых/общих модулей приложения (web
, web/master
и т.п.).
Классы внутренних библиотек прогоняются через css-modules
при сборке и защищены о того, чтобы конфликтовать или
переопределять свойства одноимённых классов из других библиотек.
Для использования в компоненте библиотеку необходимо явно подключить в основном файле компонента:
1 2 3 4 5 6 7 8 9 | import classNames from 'classnames/bind'
const cx = classNames.bind(Object.assign(
{},
require('web/master/style/webMasterLib.styl'),
require('./style/someComponent.styl')
))
const className = cx('lc-caption', 'item-label')
|
Чтобы избежать конфликта имён с локальными css-классами компонента, библиотечные классы должны содержать префикс
lc-
(или более специфичный, если есть риск конфликта имён между разными библиотеками классов).
Готовые библитеки для создания красивых UI, типа Bootstrap.
Подключаются статически прямо в заголовках index.html
приложения (например, src/apps/master/static/index.html
)
либо из публичных CDN, либо из той же папки static
приложения. Классы фреймворка,
подключенные таким образом, могут использоваться в любом компоненте приложения без явного импорта
(что не очень хорошо с точки зрения целостности и явности, но очень удобно).
Классы из внешних библиотек появляются в глобальном пространстве имён as-is.
Предупреждение
Если в собственных стилях компонента (используемых через classnames
) объявлен класс с именем, совпадающим с именем
класса в статически подключённой библиотеке, то класс из внешней библиотеки невозможно использовать посредством
функции cx
, поскольку будет подставлено преобразованное посредством css-modules
имя локального класса.
Если очень не хочется переименовывать локальный класс, то можно вставить его вручную в свойство className
элемента, без использования хелпера classnames
, либо использовать отдельный инстанс classnames
.
Во многих случаях, когда в структуре компонента используется другой (дочерний) компонент, родительскому компоненту “нужно” каким-либо образом спозиционировать дочерний относительно других своих элементов. Для этого ему нужно задать стили для корневого элемента дочернего компонента. Однако это является своего рода нарушением идеальной картины мира, когда каждый компонент задаёт стили только для своих собственных элементов и не вмешивается в стили других компонентов.
Разрешить это противоречие поможет взгляд на корневой элемент дочернего компонента как на некий кастомный html-тег в рамках родительского компонента. Ведь родительский компонент волен задавать стили для всех “не-компонентных” тегов в своём шаблоне. А чтобы не родительский компонент не вмешивался в детали внутренней реализации дочернего, представим, что этот кастомный html-тег поддерживает только ограниченный набор CSS-свойств, которые определяют позиционирование.
Отсюда следует набор правил, которые с высокой вероятностью помогут избежать противоречивых или неоднозначных стилей:
Родительский компонент имеет право задавать стили только для корневого тега дочернего путём присвоения дополнительного CSS-класса дочернему компоненту. Этот класс объявлен должен быть объявлен в пространстве имён (или зависимостях) родительского компонента, дочерний компонент о нём ничего не “знает”.
Для этого дочерний компонент должен принимать свойство className
, значение которого должно добавляться к списку
классов корневого элемента.
Родительский компонент имеет право задавать для корневого тега дочернего компонента только свойства, влияющие на позиционирование, такие как:
position
. Однако нельзя переопределять значение на position: static
, поскольку это может повлиять на
отображение элементов внутри дочернего компонента, если в нём было задано значение absolute
или relative
.z-index
margin
top
, bottom
, left
, right
Если дочерний компонент использует вышеуказанные свойства для своего корневого тега, то желательно описать эти особенности в документации и указать, какие возможности по позиционированию этого компонента предусмотрены.
Предупреждение
Это важно также с точки зрения приоритета переопределения свойств. Если в css-классе, добавленном родительским компонентом, и внутреннем классе дочернего компонента, определены разные значения одного и того же свойства, то приоритет определяется не позицией класса в списке классов DOM-элемента, а тем, в каком порядке будут расположены стили системой сборки (webpack), т.е. в общем случае — неопределённом.
Поэтому лучше документировать факт использования внутренним классом позиционных свойств, чтобы автору родительского
компонента было понятно, что следует использовать !important
для определения однозначного приоритета.
Все другие модификации внешнего вида дочернего компонента должны поддерживаться внутри самого дочернего компонента и
управляться снаружи с помощью обычных свойств (props) компонента, отличных от className
. Например:
1 2 3 | <div>
<SomeComponent highlighted />
</div>
|
1 2 3 | <div className={cx({ highlighted: props.highlighted })}>
some content
</div>
|
0
без единцы измерения..camelCase
.!important
можно использовать только там, где это действительно нужно. Злоупотреблять запрещено.