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

Циклические конструкции

В Less нет стандартных циклов for или while. Однако, текущих возможностей формирования циклических конструкций хватает для всего круга задач, связанного с вёрсткой.

Как я уже говорил ранее, циклы строятся на принципе защиты примесей с помощью условного оператора (when). Таким образом, нужно понимать, что все циклы в Less построены на принципе рекурсий или, как напоминает нам JavaScript, цикле с предусловием while (выполнение условия проверяется перед тем, как войти в тело цикла).

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

@column-name: col; // Префикс класса сетки
@column-count: 4; // Количество классов

.generate-column(@i: 1) when (@i =< @column-count) {
  [email protected]{column-name}[email protected]{i} {
    width: @i * (100% / @column-count);
  }

  .generate-column(@i + 1);
}

.generate-column();

Основную работу выполняет примесь generate-column, принимающая на вход параметр i и защищённая с помощью ключевого слова when. Отсюда следует, что вся магия циклов в Less заключается в условной конструкции и рекурсивном вызове.

Так как примесь принимает на вход параметр i, который по сути является итерационной переменной, а также у примеси указано условие, при котором она будет работать, получается, что мы создаём простой вариант рекурсии. Она будет существовать до тех пор, пока значение переменной @i не сравняется с таковым у @column-count или не будет больше него.

Заглянем внутрь примеси. Здесь формируется имя селектора по правилу: префикс + номер итерации. Уже затем для этого селектора вычисляется значение ширины, как: 100% ширины, делённое на количество колонок и умноженное на шаг итерации. Обратите внимание на строку .generate-column(@i + 1). Эта команда просит компилятор вызвать эту же примесь, но с увеличенным на единицу шагом итерации. Весь процесс повторяется вновь, но уже с увеличенным на единицу значением переменной @i. В этом и заключается магия.

Для наглядности процесса попробуем вместе с компилятором пройтись пару итераций:

/* Итерация 1 | @i: 1 | Новое значение @i: 2 */
.col-1 {
  width: 25%;
}

/* Итерация 2 | @i: 2 | Новое значение @i: 3 */
.col-1 {
  width: 25%;
}
.col-2 {
  width: 50%;
}

/* Итерация 3 | @i: 3 | Новое значение @i: 4 */
.col-1 {
  width: 25%;
}
.col-2 {
  width: 50%;
}
.col-3 {
  width: 75%;
}

/* Итерация 4 | @i: 4 | Новое значение @i: 5 */
.col-1 {
  width: 25%;
}
.col-2 {
  width: 50%;
}
.col-3 {
  width: 75%;
}
.col-4 {
  width: 100%;
}

После того, как шаг итерации достигает максимально допустимого значения, происходит выход из рекурсии и компилятор продолжает бороздить красоты нашего кода. В нашем случае этот момент наступает на четвёртом шаге рекурсии, когда новое значение переменной @i равно 5, а максимальное число шагов итераций, заданное переменной @column-count, определено как 4.

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

Обработка списка

Рассмотрим ещё один пример, где в цикле генерируются вспомогательные классы на основе списка. Этот пример вы уже встречали в главе 4, когда речь шла про списки (массивы).

Напомню его устрашающий код, который тогда мог повергнуть неподготовленного читателя в ужас:

// Настройки
@column-name: col;
@column-count: 4;
@column-prefix: xs, sm, md, lg;

// Генератор селекторов
.generate-class(@indexCount, @indexPrefix: 1)
  when
  (@indexPrefix =< length(@column-prefix)) {
  // Получаем элемент списка
  @prefix: extract(@column-prefix, @indexPrefix);

  // Формируем селектор
  [email protected]{column-name}[email protected]{prefix}[email protected]{indexCount} {
    width: @indexCount * (100% / @column-count);
  }

  // Порождаем следующую итерацию
  .generate-class(@indexCount, @indexPrefix + 1);
}

// Генератор сетки
.make-grid(@indexCount: 1) when (@indexCount =< @column-count) {
  // Вызываем генератор селекторов
  .generate-class(@indexCount);

  // Порождаем следующую итерацию
  .make-grid(@indexCount + 1);
}

// Вызываем генератор сетки
.make-grid();

Теперь будем разбираться с ним вплоть до мелочей. Начнём с того, что перепишем весь этот код с Less на JavaScript. Зачем? — скоро все поймёте.

Понимание деталей

Если абстрагироваться от Less к JavaScript, то здесь объявлены две функции, которые, как кирпичики лего, позволяют строить лего-замок. Каждая функция выполняет свою простейшую задачу и, на практике, все функции можно было бы объединить в одну, конечно, при условии, что вам нравится запутанный код.

Для понимания происходящего, проведём параллель между обычным JavaScript-кодом и примесями в Less.

Настройки

В коде, приведённом ниже, объявляется объект columnOptions, хранящий настройки генератора сетки, такие как:

  • Name — Имя столбца
  • Count — Количество столбцов
  • Prefix — Массив префиксов
// Настройки
var columnOptions = {
  name: 'col',
  count: 4,
  prefix: ['xs', 'sm', 'md', 'lg']
}

Если сравнивать с Less, то там настройки хранятся в переменных. Так, например, в JavaScript свойство объекта Prefix соответствует переменной @column-prefix в Less.

// Настройки
@column-name: col;
@column-count: 4;
@column-prefix: xs, sm, md, lg;

Вызов

Так как генерировать сетку на JavaScript никто не будет, то и вывод результатов будет осуществляться в консоль:

// Вызываем генератор сетки
console.log(makeGrid(columnOptions))

Разумеется, что в Less примесь вызывается стандартным образом:

// Вызываем генератор сетки
.make-grid();

Итак, переходим вместе с кодом в примесь make-grid и отслеживаем работу дальше.

Генератор сетки

Код ниже представляет собой полный эквивалент примеси make-grid, переписанной на классический ванильный JavaScript.

Переменная consoleReturn представляет собой массив объектов, содержащих в себе пары ключей:

  • Name — Имя текущего префикса (xs, sm..).
  • Selector — Объект, содержащий информацию о колонке (имя и ширина), который возвращает функция generateSelector.

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

Соответственно, вся полученная информация возвращается пользователю в консоль для анализа.

// Генератор сетки
var makeGrid = function(o) {
  var consoleReturn = []
  for (var index = 0; index < o.count; index++) {
    consoleReturn.push({
      name: o.prefix[index],
      selector: generateSelector(o, index)
    })
  }

  return consoleReturn
}

Теперь снова посмотрим на нашу оригинальную примесь make-grid и разберёмся с тем, что там происходит:

// Генератор сетки
.make-grid(@indexCount: 1) when (@indexCount =< @column-count) {
  // Вызываем генератор селекторов
  .generate-class(@indexCount);

  // Порождаем следующую итерацию
  .make-grid(@indexCount + 1);
}

Если исключить тот факт, что в Less данные возвращаются автоматически и в рекурсивно вызываемой примеси generate-class, то можно с уверенностью сказать, что эта примесь работает так же, как и функция, написанная на JavaScript.

Вместо цикла for здесь используется рекурсия, которая работает до тех пор, пока итерационная переменная @indexCount меньше, либо равна количеству колонок, указанному в настройках.

На каждой итерации вызывается примесь генератора классов сетки, в которую передаётся переменная, указывающая номер итерации. Далее вызывается текущая примесь со значением итерационной переменной, увеличенной на единицу, чтобы породить рекурсию. То есть принцип действия полностью соответствует примеру, рассмотренному в начале этой части.

Проводя аналогию с функцией на JavaScript, можно выделить следующие наблюдения:

  • Аналогом цикла for в Less является строка .make-grid(@indexCount + 1)
  • Аналогом выражения цикла for в Less является условие when или, как его принято называть — выражение защиты примесей.

Перейдём к рассмотрению примеси, генерирующей селекторы.

Генератор селекторов

Ниже представлен эквивалент примеси generate-class на языке JavaScript, с той лишь разницей, что создавать рекурсию в JavaScript — кощунство, поэтому тут применяется стандартный цикл for.

// Функция генерации селекторов
var generateSelector = function(o, indexCount) {
  var selectors = []
  for (var index = 0; index < o.prefix.length; index++) {
    var name = o.name + '-' + o.prefix[indexCount] + '-' + index
    var width = index * (100 / o.count) + '%'
    selectors.push({
      name: name,
      width: width
    })
  }

  return selectors
}

Эта функция принимает на вход объект настроек и номер текущей итерации. Массив selectors будет содержать имя и ширину селектора для всех префиксов из массива. Получается, что в нём будет храниться эквивалент CSS-кода:

.col-xs-1 {
  width: 25%;
}
.col-sm-1 {
  width: 25%;
}

Углубляться в то, как он будет представлен не имеет смысла, так как главное здесь — понимать суть. В итоге, из этой функции будет возвращаться массив колонок одного префикса.

Снова обратим внимание на оригинальную less-примесь и проанализируем её действие:

// Генератор селекторов
.generate-class(@indexCount, @indexPrefix: 1)
  when
  (@indexPrefix =< length(@column-prefix)) {
  // Получаем элемент списка
  @prefix: extract(@column-prefix, @indexPrefix);

  // Формируем селектор
  [email protected]{column-name}[email protected]{prefix}[email protected]{indexCount} {
    width: @indexCount * (100% / @column-count);
  }

  // Порождаем следующую итерацию
  .generate-class(@indexCount, @indexPrefix + 1);
}

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

В отличии от JavaScript кода, примесь генерирует селектор и его свойства, а также возвращает их в виде CSS-кода по правилу:

Имя колонки + текущий префикс + номер итерации

Ширина колонки вычисляется тем же методом, что и в первом примере, описанном в этой части.

Таким образом, примесь будет рекурсивно выполняться до тех пор, пока не будут созданы селекторы для всех префиксов, указанных в переменной @column-prefix.

Результаты

После того, как компилятор пройдёт все итерации для каждой примеси, он вернёт сгенерированные селекторы на то место, где была вызвана управляющая примесь make-grid.

Если не изменять определённые ранее настройки, результат будет следующим:

.col-xs-1 {
  width: 25%;
}
.col-sm-1 {
  width: 25%;
}
.col-md-1 {
  width: 25%;
}
.col-lg-1 {
  width: 25%;
}
... .col-xs-4 {
  width: 100%;
}
.col-sm-4 {
  width: 100%;
}
.col-md-4 {
  width: 100%;
}
.col-lg-4 {
  width: 100%;
}

Вывод JavaScript функций имеет приблизительно такой же вид с поправкой на то, что там данные не предоставляют ценности для CSS.

Выводы

Полностью рассмотрев два примера оценить всю природу циклических конструкций в Less нельзя. Зато можно понять их базовую сущность. Со временем вы научитесь составлять и более сложные конструкции, если это потребует поставленная задача. Да, циклы в Less имеют слегка непривычный вид для JavaScript-разработчиков, но разобравшись с ними раз и навсегда, проблем в дальнейшем не предвидится.