Статьи

Функциональные оболочки в Sass

Я не большой поклонник наличия глобальных переменных повсюду, независимо от языка. В Sass, как и в CSS, у нас нет ничего, кроме глобального пространства имен (хотя последние версии Sass постепенно предоставляют больше возможностей для определения области видимости). Это как дом с одной комнатой: как бы вы ни старались организовать свою мебель, в конце концов, в доме все еще есть только одна комната.

В чем проблема с этим? Когда дело доходит до Sass, проблема в том, что переменные могут перекрываться. Что если у вас есть переменная с именем $baseline , а используемая вами структура сетки Sass также имеет переменную $baseline ? Или, что еще хуже, что если библиотека Sass, которую вы включили, использует переменную $_ для хранения частного контента, необходимого для ее работы, и вы случайно переопределите ее своими собственными материалами? Просто: это ломается.

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

Функциональный что …?

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

Разве это не боль делать это? , ты говоришь. По-разному. В переменной есть нечто прекрасное: ее простота. Переменная очевидна: это значение, сопоставленное с ключом хранилища. Как это может быть проще, чем это? Тем не менее, я думаю, что необработанные переменные не лучше всего подходят для таких вещей, как конфигурации библиотек и тому подобное.

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

Как это работает?

Давайте рассмотрим простой вариант использования. Мы строим небольшую сеточную систему, которая поддерживает либо режим письма слева направо, либо справа налево. Из этой конфигурации наша система определяет $main-direction ( left или right ) и $opposite-direction (противоположное $main-direction ). Вот как мы могли бы написать это с переменными по умолчанию:

 // Define the content direction for the whole grid system $writing-mode: 'LTR' !default; // ... // DON'T TOUCH $main-direction: if($writing-mode != 'LTR', right, left); $opposite-direction: if($writing-mode != 'LTR', left, right); 

Какие проблемы может вызвать этот код? Давайте назовем несколько:

  • Предполагается, что $writing-mode либо LTR либо RTL , но пользователь может без проблем определить его для любого значения.
  • Если для $writing-mode установлено значение ltr , система сетки будет работать справа налево, потому что LTR != ltr .
  • Если $writing-mode недопустим, ошибки и предупреждения не выдаются. Тихая неудача.
  • При переопределении $writing-mode внутри другого контекста по любой причине следует добавить флаг !global но об этом можно забыть.

Я уверен, что мы могли бы найти несколько других недостатков, но это основные проблемы, которые мы рассмотрим в этой статье.

Сборка оболочки

Таким образом, идея состоит в том, чтобы иметь функцию (или, лучше, миксин), чтобы установить направление контента. Он удостоверится в правильности значения, возможно выдаст предупреждение / ошибку и, наконец, создаст глобальную переменную.

 /// Define the content direction for the whole grid system /// @access public /// @param {String} $value ("LTR") - Content direction, either `RTL` or `LTR` /// @throw Throws an error if the given value is neither `RTL` nor `LTR`. /// @output Nothing. @mixin content-direction($value: "LTR") { $value: to-upper-case($value); @if index("RTL" "LTR", $value) == null { @error "Content direction can be either `LTR` or `RTL` (case-insensitive). " + "Given: `#{$value}`. Aborting."; } $__direction__: $value !global; } 

Так что здесь происходит? Миксин:

  1. Убедиться, что заданное значение в верхнем регистре, прежде чем проверять его по допустимым строкам;
  2. Проверяет заданное значение в верхнем регистре как с RTL и с LTR и выдает ошибку, если оно не является одним из обоих;
  3. Динамически создает глобальную переменную с именем $__direction__ содержащую заданное значение в верхнем регистре.

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

 /// Return the content direction /// @access private /// @throw Throws an error if `content-direction` has not been included yet. /// @return {String} - Either `RTL` or `LTR` @function get-content-direction() { @if global-variable-exists("__direction__") == false { @error "Content direction has not been initialized yet. " + "Please include `content-direction($value)` before anything else."; } @return $__direction__; } 

Эта функция возвращает $__direction__ если $__direction__ content-direction уже был включен, или выдает ошибку, чтобы предложить пользователю включить content-direction раньше всего. Вы увидите, что суть этой функции состоит только в том, чтобы избежать повторения ошибки в наших двух входящих средствах доступа для основного направления расположения и противоположного направления.

Схема расположения

Когда мы хотим получить доступ либо к основному направлению макета, либо к противоположному направлению макета, мы используем любую из следующих функций:

 /// Return the layout direction /// @access public /// @require {function} content-direction /// @return {Position} - Either `left` or `right` @function get-layout-direction() { @return if(get-content-direction() == "LTR", left, right); } /// Return the layout opposite direction /// @access public /// @require {function} content-direction /// @return {Position} - Either `left` or `right` @function get-layout-opposite-direction() { @return if(get-content-direction() == "RTL", left, right); } 

То, как они работают, должно быть совершенно очевидным: они получают направление контента и возвращаются либо left либо right .

Опять же, мы могли бы проверить $__direction__ напрямую, а не создавать функцию get-content-direction() однако мы не смогли бы предоставить полезное сообщение об ошибке, если пользователь попытается получить направление макета до определения направления content-direction с content-direction Вместо этого пользователь увидел бы:

Неопределенная переменная: «$ __ direction__».

Не очень полезно, не так ли? Тем более что пользователь абсолютно не знает, что такое $__direction__

Используй это

 // Define the content direction for the grid @include content-direction("RTL"); // Framework stuff %grid-whatever-tool { margin-#{get-layout-direction()}: 1em; } 

Последние мысли

Я допускаю, что может показаться немного излишним иметь 3 функции и 1 миксин для инициализации 3 переменных, и в большинстве случаев я не мог согласиться с этим утверждением.

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

Кроме того, когда в системе есть несколько опций, становится очень легко обернуть их все в карту и проверить любое значение из этой карты в миксине по умолчанию, что приводит к довольно аккуратному API:

 @include set-options(( 'baseline': 42px, 'debug': false, 'color': hotpink )); 

Кстати, именно так пошли Compass , Susy и некоторые другие продвинутые фреймворки Sass, так что вы можете сказать, что это не такая уж плохая идея. 😉

Вы можете играть с кодом на SassMeister .