7 техник Sass для написания хорошего кода

sass

Andrius Juskenas, разработчик со стажем, опубликовал статью, в которой описывает 7 техник Sass, позволяющие писать код лучше. Редакция techrocks.ru публикует адаптированный перевод материала.


Sass (Syntactically Awesome Stylesheets) — это мощный инструмент, позволяющий с легкостью писать CSS. Если вы читаете эту статью, скорее всего, вы уже имеете представление о Sass. В данном материале я буду постараюсь рассказать о самых продвинутых его возможностях. Ниже приведены 7 техник Sass, помогающих разработчикам писать более эффективный код.

В действительности, техник не 7, а 6. Потому что первая — на самом деле, не совсем техника, а больше стратегия использования имен. Это ключевой момент для эффективной разработки. Но если вы уже используете какую-то другую стратегию, которая работает для вас, пожалуйста, напишите о ней в комментариях к данной статье.

Создание компонентов

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

Существует множество различных стратегий, но после многократных попыток их применения, я выбрал одну, которая лучше всего сработала для Devbridge Group — это стратегия компонентов. Компоненты — это независимые элементы, которые можно многократно использовать в различных местах. Это могут быть небольшие элементы, как, например, “Button”, “Input” или большие — например, “Article.” Главное — сделать их независимыми: не создавайте страницы или модули, зависящие от контента. Делайте так, чтобы они представляли объект, тип графического элемента или функцию и называйте их так, чтобы легко было понять, что они означают (избегайте сокращений, например, btn, frm, ppl, и т.д.).

Что касается структуры файлов: рекомендуется создавать один файл для одного компонента. Это вначале может показаться странным, но в итоге такой подход позволяет в будущем найти необходимый компонент намного быстрее. Воспринимайте файл как класс в ООП, или, если вы знакомы с ReactJS, как React компонент. Если же у вас сотни и тысячи небольших компонентов — создавайте папки. Например, вы можете положить все компоненты формы в папку /form-elements.

Sass components

Другая проблема — инкапсуляция стилей. Иногда нужно сделать небольшие изменения — допустим, нужно сделать кнопку шире или выше. В таком случае, вы возможно захотите переопределить ее как “.my-custom-page .button”. Однако в дальнейшем такой подход может вызвать путаницу — вам будет тяжело поддерживать код с большим количеством переопределений. Вместо этого, измените сам компонент, например: “.button—large”.

Лучший способ придерживаться этого шаблона, на мой взгляд — это использовать стратегию именования CSS классов BEM (если вы не в курсе, что такое BEM, вы можете прочитать здесь). BEM хорошо подходит для Saas. Используя селектор & и вложенность, вы легко сможете сконструировать модули.

.article {
    width: 100px;
     
    &__title {
        font-size: 20px;
        font-weight: 500;
    }

    &--wide {
        width: 200px;
    }
}

Локальные переменные и mixins

Переменные и mixins в Sass по умолчанию являются глобальными. Если вы определили $primary-color, то его значение можно использовать везде, что не всегда хорошо. Например, у вас есть статичное значение (фиксированная высота), и вы используете его только для самого компонента и его дочерних элементов. Первый способ (и не самый эффективный) — это записать значение в глобальную переменную. Такой подход в дальнейшем может сильно увеличить список глобальных переменных, которые к тому же будут очень специфичны.

Более эффективное решение — локальные переменные. Можно задать переменную внутри блока селектора, рядом со свойствами. Таким образом, она будет доступна только внутри селектора.

Такой же подход применим и для mixins. Вы можете не только создавать глобальные mixins, но можете также использовать локальные, когда необходимо создать какие-то повторяющиеся действия или элементы внутри компонента. Реальный пример — необходимость пересчитать что-то на каждой breakpoint, но только с другим значением высоты/ширины.

.filters-block {
    $_filter-height: 20px;
    
        @mixin _note-size($value) {
        height: $value;
        line-height: $value;
        margin-top: -($value);
    }
    &__list {
        height: $_filters-height * 7; // Display 7 filters
        overflow: auto;
    }
    &__item {
        height: $_filter-height;
    }
    &__note {
        @include _note-size(30px);
    }
    @media screen and (max-width 1000px) {
        &__note {
            @include _note-size(40px);
        }
    }
}

Еще один момент — всегда пытайтесь вначале создать локальные mixins и переменные в новом компоненте. Это сохраняет инкапсуляцию, а также улучшает читабельность, когда у вас все в одном месте. Делайте их глобальными только в случае, если нужно делиться ими между компонентами.

Для обозначения локальных переменных, я рекомендую использовать нижнее подчеркивание “_” перед названием переменной/mixin, так же как это делается в других языках программирования.

Используйте карты или списки

Карты — это Sass переменные-объекты. Мы используем их для глобальных layout значений, z- индексов, и других глобальных конфигураций. Но зачем нам использовать карты, если есть обычные переменные? Главное преимущество карт — это читабельность. Вместо длинного списка глобальных переменных (который не сильно удобно читать), у вас появляется объект с логической структурой.

Представьте, что у вас есть два layouts — для мобильных устройств и для десктопа. Используя переменные, можно создать $header-height—desktop, $header-height—mobile, и т.д. Мало того, что имя странное, так вы еще и должны держать их вместе для читабельности. Если использовать карты, вы можете задать две отдельные карты — $desktop-layout и $mobile-layout с различными значениями—что сделает ваш код удобочитаемым и легко управляемым.

$mobile-layout: (
    layout-values: (
        header: (
            height: 72px
        ),
        sidebar: (
            width: 100%
        )
    )
);
$desktop-layout: (
    layout-values: (
        header: (
            height: 86px
        ),
        sidebar: (
            width: 300px
        )
    )
);

Самое сложное здесь — это получение значений, особенно в случае, когда вы используете вложенные карты. В этом случае надо создавать функцию для получения таких значений. Сначала вы потратите немного времени, но зато впоследствии, вы просто будете вызвать эту функцию. Чтобы сэкономить ваше время, вот здесь можете найти пару функций, которые мы используем на нашем проекте для получения данных из карт/списков.

Еще один частая проблемы карт (или листов) — это значения z-индексов. Очень тяжело поддерживать все эти z-индексы различных компонентов. Будет становится все сложнее по мере роста проекта. У вас на проекте есть z-индекс: 9999? У нас нет. Мы определяем все z-индексы в листе в одном месте. И очень легко сделать один компонент выше другого — нужно просто поменять значения в карте — и получишь результат.

$z-indexes: (
    error,
    modal,
    header,
    sidebar,
    footer
);

Переменная $this

Типичная проблема при создании self-controlled компонентов (особенно при использовании методологии BEM) — это создание селекторов внутри модификаторов. Если у вас есть модификатор, который меняет свойства дочернего элемента, ваш код скорее всего будет выглядеть примерно так:

.filter-block {
    &__title {
        color: black;
    }
        
    &--expandable {
        .filter-block__title {
            color: blue;
        }
    }
}

Здесь нет ничего плохого, но вы дублируете имя селектора несколько раз. Чтобы это оптимизировать, вы можете использовать шаблон Javascript: помните «var self = this»? К счастью, нам не надо это больше делать с EcmaScript 6, но вы можете использовать этот шаблон для Sass.

Можно хранить родительский селектор как локальную переменную —$this: & вверху блока селектора. Оператор & в Sass возвращает полный селектор, и в данном случае вы фиксируете его на верхнем уровне. Вот код, написанный по подобному шаблону:

.filter-block {
    $this: &;
    &__title {
        color: black;
    }
    &--expandable {
        #{$this}__title {
            color: blue;
        }
    }
}

Важно держать компонент на верхнем уровне. Несложно понять, что если ваш блок вложенный, например .otherblock .myblock$this из .myblock вернет полное значение, включая родителя, таким образом этот подход не будет работать правильно.

Умные breakpoints

Предположим, вы храните layout значения в картах. Вы должны изменить layout на определенной breakpoint, таким образом вы определяете два layout карт: $small-layout и $big-layout. Но как вы получаете нужные значения, если у вас только одна функция (описанная в параграфе ‘Используйте карты или списки’), которая возвращает вложенные значения из дефолтной карты (скорее всего из $big-layout)? Создать отдельную функцию для получения $small-layout? Добавить layout карту, как дополнительный параметр для существующей функции?

Есть идея получше: создать breakpoint mixin. Этот шаблон взят из Susy, фреймворка для layout и grids. С тех пор как Susy использует карты для layout, на “susy-breakpoint” mixin вы можете задать layout карту как необязательный параметр. Ничего сверхъестественного не произойдет, но когда вы будет использовать другие функции (например gutter()) внутри breakpoint mixin, она автоматически возвратит значения в нужной layout карте. “Умный” breakpoint работает таким же образом, но использует специальную функцию для получения карты. Это даже может быть обертка для susy-breakpoint если вы используете Susy (для использования такого же Layout для обоих).

$small-layout: (
    header: (
        height: 30px
    )
);
$big-layout: (
    header: (
        height: 60px
    )
);
$default-layout: $big-layout;
$current-layout: $default-layout !default;
@mixin smart-breakpoint($breakpoint, $layout) {
    $_temp: $current-layout;
    $current-layout: $layout !global;
    @media screen and ($breakpoint) {
        @content;
    }
    $current-layout: $_temp !global;
}
.header {
    height: layout-value(header, height); // the value will be 60px
      @include smart-breakpoint('max-width: 768px', $small-layout) {
        height: layout-value(header, height);  // the value will be 30px
    }
}

Работающий код можно найти здесь.

Можете поиграть с кодом и посмотреть, как он работает, но в принципе mixin получает layout как параметр и переопределяет глобальное значения, добавляя !global флаг вначале. Когда вы используете функцию карты внутри breakpoint mixin, новое значение становится значением по умолчанию и вы получаете желаемый результат. До того, как закончить работу, mixin изменяет значение по умолчанию.

Повторяющийся цикл

Sass предоставляет @for и @each методы для повторяющихся циклов. Но для чего они нужны?

Допустим, у вас есть 10 модификаторов внутри блока. Все они делают одно и то же: изменяют иконку или другое свойство. Написание всех этих модификаторов вручную — не лучшее решение; и вместо этого вы можете использовать вышеупомянутые шаблоны. С локальными переменными и картами вы сможете получить чистый и простой в обслуживании код.

В следующем примере, иконки хранятся как карта (имя класса как ключ и иконка как значение):

.icon {
    $_icons: (
        delete: icn-delete,
        edit: icn-edit,
        add: icn-add
    );
    &::before {
        content: '';
    }
    @each $label, $icon in $_icons {
        &--#{$label} {
            &::before {
                @include svg-sprite($icon);
            }
        }
    }
}

Переменные второго уровня для цветов

Возможно самое большое преимущество глобальных переменных Sass — это возможность определять цвета и использовать их везде, без повторения HEX значения. Однако существует множество различных стратегий наименований цветов, и очень трудно выбрать лучшую.

Мы используем $color-[color]-[number] шаблон (например, $color-green-1) как название первого уровня. Этот шаблон легко использовать, так как цвета всегда начинаются с префикса «color-«, особенно, если вы используете подсказки на вашей IDE. Вы даже можете группировать их по оттенкам (от светлого до темного), так как на Material UI. Однако, использование таких переменных может быть не совсем удобным, потому что у них нет какого-либо семантического значения. Чтобы как-то решить эту проблему, используйте наименования второго уровня, где есть связь с функцией или объектом — например, $color-text-primary, $color-action, и т.д. Такие переменные должны быть связаны с перменными первого уровня, чтобы оставлять возможность использования обоих.

$color-black: #000;
$color-white: #fff;
$color-gray: #bababa;

$color-text-primary: $color-black;
$color-text-secondary: $color-gray;
$background-color-default: $color-white;

Однако будьте осторожны при создании имен второго уровня. Определяйте только самые нужные цвета. Если у вас более 10 переменных второго уровня, будет сложно читать. Более того, через некоторое время вы забудете все эти переменные, и такой подход не даст вам никаких преимуществ: всё равно нужно будет смотреть в список переменных каждый раз, когда будет нужен цвет.

Заключение

Структурирование кода при помощи компонентов помогает писать эффективный и легко управляемый код. Остальные шаблоны позволяют уменьшить дублирование кода, улучшают читабельность или просто помогают делать вещи легче. Однако, главное правило — все должно быть просто и последовательно. Не используйте эти шаблоны (или еще какие-то другие интересные функции Sass) просто потому что они интересные — используйте их для решения реальных проблем.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх