CSS-live.ru

ES6: литералы шаблона изнутри

Перевод статьи ES6 Template Literals in Depth  с сайта ponyfoo.com, опубликовано на css-live.ru с разрешения автора — Николаса Беваквы.

Вчера мы рассмотрели «Деструктурирование в JavaScript (ES6) изнутри», и некоторые наиболее распространённые примеры его использования. Сегодня мы познакомимся с литералами шаблона. Что они из себя представляют, как их использовать и для чего.

Литералы шаблона — новая возможность в ES6 для более удобной работы со строками и шаблонами строки. Оберните текст в `обратные кавычки` и получите описанные ниже возможности.

  • В них можно интерполировать переменные
  • На самом деле можно интерполировать строки с любыми выражениями, не только переменные
  • Они могут быть многострочными. Наконец-то!
  • Можно создавать необработанные шаблоны, которые не интерпретируют обратные кавычки

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

Давайте углубимся в литералы шаблона и посмотрим, что можно придумать.

Применение литералов шаблона

Мы уже рассматривали базовый пример `Я обычная строка`. Первый плюс литералов шаблона, который стоит отметить — теперь можно объявлять строки с любыми кавычками, и одинарными, и двойными, и при этом не надо ничего экранировать

var text = `Ого, сколько разных кавычек, и прямо "из коробки"! Я прямо как д'Артаньян!`

Это конечно здорово, но литералы шаблона способны и на большее. Как насчёт реальной интерполяции? Для этого можно использовать нотацию ${expression}.

var host = 'ponyfoo.com'
var text = `этот блог живёт на ${host}`
console.log(text)
// <- 'этот блог живёт на ponyfoo.com'

Выше я заметил, что на месте ${host} могут быть какие угодно выражения. Представляйте себе, что какое бы выражение вы ни вставляли, переменная объявляется еще до запуска шаблона, а потом это значение конкатенирует с остальной строкой. Поэтому все используемые переменные, вызываемые методы и т.д. должны находиться в текущей области видимости.

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

var text = `этот блог живёт на ${'ponyfoo.com'}`
console.log(text)
// <- 'этот блог живёт на ponyfoo.com'
var today = new Date()
var text = `время и дата ${today.toLocaleString()}`
console.log(text)
// <- 'время и дата 8/26/2015, 3:15:20 PM'
import moment from 'moment'
var today = new Date()
var text = `сегодня ${moment(today).format('Do [of] MMMM')}`
console.log(text)
// <- 'сегодня 26 августа'
var text = `Такое вот неопределенное выражение ${Infinity/0}`
console.log(text)
// <- 'Такое вот неопределенное выражение'

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

var text = (
  'foo\n' +
  'bar\n' +
  'baz'
)
var text = [
  'foo',
  'bar',
  'baz'
].join('\n')

Теперь вместо этого можно просто использовать обратные кавычки! Заметьте, что пробелы имеют значение, так что вам по-прежнему могут понадобиться скобки, чтобы отделить первую строку текста от объявления переменной.

var text = (
`foo
bar
baz`)

Многострочные шаблоны проявляют себя во всей красе, когда, к примеру, у нас есть кусок HTML, в который нам хочется интерполировать переменные. Подобно JSX, можно воспользоваться выражением для перебора коллекции и вернуть в return другой литерал шаблона, чтобы объявить элементы списка. Это очень удобно для объявления субкомпонентов в ваших шаблонах. Обратите также внимание, как я использую деструктирование, чтобы не писать article перед каждым своим выражением — хорошо, когда есть «этакий блок with, только нормальный»

var article = {
  title: 'Привет, литералы шаблона',,
  teaser: 'Интерполяция строки прекрасна. Вот некоторые возможности',,
  body: 'Куча HTML без опасных символов',
  tags: ['es6', 'template-literals', 'es6-in-depth']
}
var {title,teaser,body,tags} = article
var html = `<article>
  <header>
    <h1>${title}</h1>
  </header>
  <section>
    <div>${teaser}</div>
    <div>${body}</div>
  </section>
  <footer>
    <ul>
      ${tags.map(tag => `<li>${tag}</li>`).join('\n      ')}
    </ul>
  </footer>
</article>`

Результат этого кода показан ниже. Заметьте, что пары пробелов хватило, чтобы у <li> получились нормальные отступы.

<article>
  <header>
    <h1>Привет, литералы шаблона</h1>
  </header>
  <section>
    <div>Интерполяция строки прекрасна. Вот некоторые возможности</div>
    <div>Куча HTML без опасных символов</div>
  </section>
  <footer>
    <ul>
      <li>es6</li>
      <li>template-literals</li>
      <li>es6-in-depth</li>
    </ul>
  </footer>
</article>

Необработанные шаблоны по сути то же самое, необходимо просто дописать перед литералом шаблона с String.raw. В некоторых случаях это может быть очень удобно.

var text = String.raw `Символ новой строки "\n" не превратится в новую строку.
Он будет экранирован.`
console.log(text)
// "\n" новая строка не станет новой.
// Он будет экранирован.

Возможно вы заметили, что String.raw кажутся особой частью синтаксиса литерала шаблона, и вы правы! Выбранный вами метод будет применяться для парсинга шаблона. Методы литерала шаблона — называемые «Помеченными шаблонами» — принимают массив, содержащий список статичных частей шаблона, а также каждое выражение в их собственных переменных.

Например, литерал шаблона `Привет, ${name}. Я ${emotion}!` передаст аргументы «помеченного шаблона» в вызов функции, как показано ниже

fn([‘Привет, ‘, ‘. Я ‘, ‘!’], ‘nico’, ‘озадачен

Порядок, в котором идут аргументы, может показаться странным, но всё становится на свои места, если взглянуть на это так: для каждого элемента в массиве шаблона (кроме последнего — прим. перев.) есть результат выражения после него.

О помеченных шаблонах понятно

Ниже я привёл пример normalного метода, который работает точно так же, как и поведение по умолчанию. Это поможет лучше понять, что происходит под капотом шаблонных литералов.

Если вы не знаете, что делает .reduce, зайдите на MDN или в мою статью «Развлечения с настоящими массивами». Этот метод бывает очень кстати, когда нужно свести коллекцию к одному значению, вычисляемому на ее основе.

В данном случае можно сокращать шаблон template, начиная от template[0], и затем сокращая все другие части, добавляя предыдущее выражение expression и последующую часть part.

function normal (template, ...expressions) {
  return template.reduce((accumulator, part, i) => {
    return accumulator + expressions[i - 1] + part
  })
}

Синтаксис ...expressions — также новинка ES6. Он называется «синтаксисом оставшихся параметров», и фактически кладёт все аргументы, переданные в normal после template, в один массив. Попробуйте нижеприведённый помеченный шаблон, и вы заметите, что получите тот же результат, что и при опущенном normal.

var name = 'nico'
var outfit = 'пальто'
var text = normal`привет ${name}, ты сегодня выглядишь потрясающе в этом ${outfit}`
console.log(text)
// <- 'привет, nico, ты сегодня выглядишь потрясающе в этом пальто'

Выяснив, как работают помеченные шаблоны, что мы можем с ними делать? Ну, что захотим. Например, можно вывести полученные данные заглавными буквами, придав нашему приветствию более сатирический вид — Я мысленно прочитал результат голосом фокусника Гоба из ситкома «Замедленное развитие», теперь вот сижу и ржу в голос, как псих. Эх, не надо было этого делать.

function upperExpr (template, ...expressions) {
  return template.reduce((accumulator, part, i) => {
    return accumulator + expressions[i - 1].toUpperCase() + part
  })
}
var name = 'nico'
var outfit = 'пальто'
var text = upperExpr`привет ${name}, ты сегодня выглядишь потрясающе в этом ${outfit}`
console.log(text)
// <- 'привет, NICO, ты сегодня выглядишь потрясающе в этом ПАЛЬТО'

Очевидно, помеченные шаблоны можно использовать не только для приколов над самим собой, но и для чего-то более полезного. По сути, от них можно с ума сойти. Определенно полезным применением было бы автоматически очищать пользовательские данные в ваших шаблонах. Если у нас есть шаблон, в котором все выражения рассматриваются как полученные данные, мы могли бы использовать insane, чтобы очистить выражения от ненужных тегов HTML.

import insane from 'insane'
function sanitize (template, ...expressions) {
  return template.reduce((accumulator, part, i) => {
    return accumulator + insane(expressions[i - 1]) + part
  })
}
var comment = 'хаха, xss — это так легко< iframe src="http://evil.corp">< /iframe>'
var html = sanitize`< div>${comment}< /div>`
console.log(html)
// < - '< div>хаха, xss — это так легко< /div>'

Теперь не так легко!

Когда-нибудь я наверняка буду начинать и заканчивать абсолютно все строки в JavaScript исключительно обратными кавычками.

P.S. Это тоже может быть интересно:

1 комментарий

  1. Привет!
    Поправьте шаблон функции fn([Привет, ‘, ‘. Я ‘, ‘!’], ‘nico’, ‘озадачен) на fn([Привет, ‘, ‘. Я ‘, ‘!’], ‘nico’, ‘озадачен)

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

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

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