Перейти к содержанию

Интерполяция переменных

Если вы раньше работали с PHP или Ruby, то должны были хотя бы раз использовать интерполяцию переменных. Интерполяция переменных — это получение значения одной переменной в зависимости от другой или других переменных.

В Less есть конструкции типа @{}, которые технически похожи на интерполяцию переменных. Такие конструкции могут быть использованы в селекторах, переменных и строках. Далее будут рассмотрены все популярные и почти не применяемые варианты использования интерполяции переменных.

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

Интерполяция переменных в строках

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

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

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

@russia-city-odintsovo: Одинцово;
@english-city-odintsovo: Odintsovo;
@russia-hello: 'Привет, @{russia-city-odintsovo}!';
@english-hello: 'Hello, @{english-city-odintsovo}!';

Конструкции @{russia-city-odintsovo} и @{english-city-odintsovo} интерполируются и если вызвать эти переменные, то вместо них будет подставлено название города на соответствующем языке:

.hello-russia {
  content: 'Привет, Одинцово!';
}
.hello-english {
  content: 'Hello, Odintsovo!';
}

Пример 3.2.1

В этом примере мы подробно разберём то, как происходит интерполяция переменных. Инициализируем несколько переменных и поместим их в начале файла _styles.less так, как это было описано в начале главы:

// Variables
@icon-font-name: fontawesome-webfont;
@icon-font-path: '../fonts';

Чтобы воспользоваться шрифтом FontAwesome, в проекте необходимо использовать директиву @font-face. Ради экономии места я не стал вставлять сюда весь код примера, он, как и любой код в примерах, будет доступен в архиве.

@font-face {
  font-family: 'FontAwesome';
  src: url('@{icon-font-path}/@{icon-font-name}.eot?v=4.3.0');
  src: url('@{icon-font-path}/@{icon-font-name}.eot?#iefix&v=4.3.0') format('embedded-opentype'),
    ...;
}

На выходе компилятора получим следующий код:

@font-face {
  font-family: 'FontAwesome';
  src: url('../fonts/fontawesome-webfont.eot?v=4.3.0');
  src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),
    ...;
}

Обратите внимание, если поменять вызов переменной @{icon-font-path} на @icon-font-path то, вместо того, чтобы вставить значение переменной, компилятор попросту принял @icon-font-path за часть строки. Именно в этом и заключается суть интерполяции переменной — грубо говоря, она заставляет интерпретатор отличать строки от переменных и при необходимости подставлять их значение в строку.

@font-face {
  font-family: 'FontAwesome';
  src: url('@icon-font-path/fontawesome-webfont.eot?v=4.3.0');
  src: url('@icon-font-path/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),
    ...;
}

Интерполяция переменных в селекторах и свойствах

Иногда требуется делать код гибким вплоть до того, чтобы селекторы изменяли своё имя в зависимости от значения переменных. Наиболее вероятно, что такое поведение пригодится в библиотеках или фреймворках, в которых разработчики хотят разрешить изменять префикс классов их детища.

Интерполяция переменных в селекторах по конструкции полностью повторяет таковую в строках:

@lang-prefix: site;
@lang-russia: rus;
@lang-english: eng;

[email protected]{lang-prefix}-@{lang-russia} {
  content: '@{lang-russia}';
}

[email protected]{lang-prefix}-@{lang-english} {
  content: '@{lang-english}';
}

Компилятор сам позаботится о формировании имени класса из двух переменных, инициализированных ранее:

.site-rus {
  content: 'rus';
}
.site-eng {
  content: 'eng';
}

Никто не запрещает использовать интерполяцию и в рамках свойств. Конструкция вида:

@property: color;

.widget {
  @{property}: #0ee;
  [email protected]{property}: #999;
}

будет компилироваться в:

.widget {
  color: #0ee;
  background-color: #999;
}

Интерполяция переменных внутри переменных

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

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

Пример 3.2.2

Так как примеси мы ещё не проходили, а интерполяцию переменных внутри переменных показать и объяснить нужно, то представим себе ситуацию, в которой у нас есть несколько переменных:

// Variables
@color-prefix: color;

@color-grey: #fafafa;
@color-red: #ffebee;

Разработчику нужно выбрать серый и красный цвет, но префикс переменных, в которых хранится искомое значение, ему не известен — он хранится в переменной @color-prefix.

Чтобы получить цвета, разработчику нужно построить переменную на основе уже известной ему переменной:

.grey {
  background-color: e('@{@{color-prefix}-grey}');
}

.red {
  background-color: e('@{@{color-prefix}-red}');
}

Обратите внимание на то, что здесь используется экранирование, так как конструкция внутри очень похожа на конструкцию переменной, но в то же время не являющаяся ей. Далее нужно обернуть конструкцию в @{} столько раз, сколько раз планируется собирать переменную из частей. Проще говоря, мы конструируем строку, а потом получаем значение переменной, имя которой совпадает с этой строкой.

Однако, ложкой дёгтя является то, что при такой интерполяции значение всегда приводится к строке. Это поведение стоит отнести к частному случаю, который также имеет своё решение, пусть и не самое элегантное.

Частый случай интерполяции переменных внутри переменных

В некоторых случаях появляется необходимость получить значение переменной, имя которой задано в другой переменной. Примером может служить примесь, которая выставляет отступы блока в зависимости от переданной в неё строки. Рассмотрим эту проблему более детально.

Допустим, разработчик опытным путем или на глаз определил, что все блоки на странице делятся на два типа: с маленьким и большим отступом. Здесь под отступом стоит понимать, как внутренний, так и внешний отступ. Для стандартизации кода эти размеры будут вынесены в переменные:

// Small
@margin-small: 40px;
@padding-small: 20px;

// Large
@margin-large: 80px;
@padding-large: 40px;

В будущем разработчик напишет примесь, но, так как сейчас я о примесях ещё ничего не говорил, вместо неё будем использовать переменную @size. В этой переменной будет содержаться имя размера блока, то есть small или large. Попробуем получить отступы для произвольного блока, используя описанный выше метод интерполяции переменных внутри переменных:

// Small
@margin-small: 40px;
@padding-small: 20px;

@size: 'small';

.block {
  margin: ~'@{[email protected]{size}}';
}

Этот код будет скомпилирован в CSS без ошибок и свойству margin будет присвоено значение 40px. Однако, в этой синтаксической конструкции скрывается немного чёрной магии. Понять проблему поможет простая операция сложения, которую необходимо провести с интерполяцией. Давайте попробуем сложить внешний и внутренний отступ.

// Small
@margin-small: 40px;
@padding-small: 20px;

@size: 'small';

.block {
  margin: ~'@{[email protected]{size}}' + ~'@{[email protected]{size}}';
}

Вот это поворот! Ошибка!

ParseError: Unrecognised input in _styles.less on line 8, column 32:
7 .block {
8   margin: ~"@{[email protected]{size}}" + ~"@{[email protected]{size}}";
9 }

Не буду томить читателя своими догадками и анализом происходящего — Less не умеет складывать строки. Функция экранирования возвращает строку. Да, вот так вот просто — сложить строку со строкой или строку с числом в Less нельзя. По крайне мере, в версии 2.7.1 это сделать нельзя. Впрочем, эта возможность присутствует в Sass и Stylus.

Так как же решить возникшую проблему? — использовать интерполяцию переменных с помощью конструкции @@. К сожалению, для этого придётся ввести дополнительную переменную для каждого слагаемого.

// Small
@margin-small: 40px;
@padding-small: 20px;

@size: 'small';

.block {
  @margin: '[email protected]{size}';
  @padding: '[email protected]{size}';
  margin: @@margin + @@padding;
}

После компиляции свойству margin будет присвоено значение 60px. Это происходит вследствие того, что в переменной @margin после интерполяции находится имя переменной margin-small. Точно такая же ситуация со значением в переменной @padding. При вызове переменной с помощью конструкции @@ на её место подставляется значение переменной, имя которой указано в строке.