- PVSM.RU - https://www.pvsm.ru -

Путь к пониманию шаблонных литералов в JavaScript

Спецификация ECMAScript, вышедшая в 2015 году (ES6 [1]), добавила в JavaScript новую возможность — шаблонные литералы (template literals). Шаблонные литералы дают нам новый механизм создания строковых значений [2]. Этот механизм отличается множеством мощных возможностей, среди которых — упрощение создания многострочных конструкций и использование местозаполнителей для внедрения в строки результатов вычисления выражений. Кроме того, тут имеется и ещё одна возможность — теговые шаблоны (tagged template literals). Это — расширенная форма шаблонных литералов. Теговые шаблоны позволяют создавать строки с использованием выражений, находящихся внутри строк, и с применением особых функций. Всё это расширяет возможности программистов по работе со строками, позволяя, например, создавать динамические строки, которые могут представлять собой URL [3], или писать функции для тонкой настройки HTML-элементов [4].

Путь к пониманию шаблонных литералов в JavaScript - 1 [5]

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

Объявление строк

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

Строку в JavaScript можно представить как последовательность символов, заключённую в одинарные кавычки (' '):

const single = 'Every day is a good day when you paint.'

Ещё один вариант объявления строк заключается в использовании двойных кавычек (« «):

const double = "Be so very light. Be a gentle whisper."

В JavaScript между такими строками нет серьёзных различий. В других языках использование разных кавычек при объявлении строк может означать, например, то, что строки одного вида можно интерполировать, а другие — нет. Здесь мы понимаем под «интерполяцией» возможность вычисления значений выражений-местозаполнителей, играющих роль динамических частей строк и участвующих в формировании итоговых строковых значений.

Какие именно строки использовать, объявленные с одинарными или с двойными кавычками, это, по большей части, дело личных предпочтений программиста и используемых им соглашений по написанию кода. Но если в строке, ограниченной кавычками одного из этих видов, встречаются кавычки того же вида, их нужно экранировать [6]. Кавычки же другого вида в подобных строках в экранировании не нуждаются.

// Экранирование одинарных кавычек в строке, ограниченной одинарными кавычками
const single = '"We don't make mistakes. We just have happy accidents." - Bob Ross'

// Экранирование двойных кавычек в строке, ограниченной двойными кавычками
const double = ""We don't make mistakes. We just have happy accidents." - Bob Ross"

console.log(single);
console.log(double);

Вызов пары методов log() приведёт к тому, что в консоль [7] попадут две одинаковых строки.

"We don't make mistakes. We just have happy accidents." - Bob Ross
"We don't make mistakes. We just have happy accidents." - Bob Ross

Шаблонные литералы, с другой стороны, объявляют с использованием обратных кавычек (` `):

const template = `Find freedom on this canvas.`

Здесь не нужно экранировать одинарные или двойные кавычки:

const template = `"We don't make mistakes. We just have happy accidents." - Bob Ross

Но обратные кавычки в таких строках экранировать необходимо:

const template = `Template literals use the ` character.`

Шаблонные литералы имеют все возможности, которые есть у обычных строк. Поэтому вы, вероятно, можете заменить все строки в своём проекте на шаблонные литералы и при этом ничего не потерять. Однако чаще всего в соглашениях, касающихся написания кода, указывают, что шаблонные литералы должны использоваться только тогда, когда нужны их особые возможности. Обычные же строки всегда, ради поддержания единообразия кода, объявляют с использованием или одинарных, или двойных кавычек. Кодовую базу проекта, при написании которой следуют этому стандарту, будет легче читать разработчикам, с ней ранее не знакомым.

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

Многострочные строки

В этом разделе мы сначала поговорим о том, как многострочные строки объявлялись до ES6, а потом остановимся на том, как решение этой задачи упрощают шаблонные литералы.

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

const address =
  'Homer J. Simpson' +
  '742 Evergreen Terrace' +
  'Springfield'

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

Homer J. Simpson742 Evergreen TerraceSpringfield

Ещё один подход к записи подобных строк в редакторах кода заключается в использовании символа обратной косой черты (), который ставится в конце фрагментов строк, и после которого, с новой строки, располагаются новые фрагменты:

const address =
  'Homer J. Simpson
  742 Evergreen Terrace
  Springfield'

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

Homer J. Simpson  742 Evergreen Terrace  Springfield

Создать настоящую многострочную строку можно, используя символ перевода строки (n):

const address =
  'Homer J. Simpsonn' +
  '742 Evergreen Terracen' +
  'Springfield'

При выводе в консоль строкового значения, хранящегося в address, это значение будет занимать несколько строк:

Homer J. Simpson
742 Evergreen Terrace
Springfield

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

const address = `Homer J. Simpson
742 Evergreen Terrace
Springfield`

Если вывести эту константу в консоль, то выглядеть текст будет так же, как в редакторе:

Homer J. Simpson
742 Evergreen Terrace
Springfield

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

const address = `Homer J. Simpson
                 742 Evergreen Terrace
                 Springfield`

Хотя такой стиль написания кода упрощает его чтение, то, что попадёт в консоль после его вывода, будет выглядеть не очень-то привлекательно:

Homer J. Simpson
                 742 Evergreen Terrace
                 Springfield

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

Интерполяция выражений

Раньше, до выхода ES6, для создания динамических строк, в формировании которых участвовали значения переменных или выражения, использовалась конкатенация:

const method = 'concatenation'
const dynamicString = 'This string is using ' + method + '.'

Если вывести dynamicString в консоль, то получится следующее:

This string is using concatenation.

При использовании шаблонных литералов выражения можно внедрять в строку с использованием местозаполнителей. Местозаполнитель представляет собой конструкцию вида ${}. При этом всё, что содержится в фигурных скобках, рассматривается как JavaScript-код, а всё, находящееся за пределами этой конструкции, рассматривается как строка:

const method = 'interpolation'
const dynamicString = `This string is using ${method}.`

При выводе dynamicString в консоль получится следующий результат:

This string is using interpolation.

Распространённый пример встраивания значений в строки — это создание динамических URL. Использование для этой цели конкатенации приводит к появлению громоздких и неудобных конструкций. Например, вот функция [9], которая генерирует строку доступа OAuth [10]:

function createOAuthString(host, clientId, scope) {
  return host + '/login/oauth/authorize?client_id=' + clientId + '&scope=' + scope
}

createOAuthString('https://github.com', 'abc123', 'repo,user')

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

https://github.com/login/oauth/authorize?client_id=abc123&scope=repo,user

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

function createOAuthString(host, clientId, scope) {
  return `${host}/login/oauth/authorize?client_id=${clientId}&scope=${scope}`
}

createOAuthString('https://github.com', 'abc123', 'repo,user')

Результат работы функции будет таким:

https://github.com/login/oauth/authorize?client_id=abc123&scope=repo,user

Для того чтобы убрать пробелы в начале и в конце строки, создаваемой с помощью шаблонного литерала, можно воспользоваться методом trim() [11]. Например, в следующем фрагменте кода для создания HTML-элемента с настраиваемой ссылкой используется стрелочная функция [12]:

const menuItem = (url, link) =>
  `
<li>
  <a href="${url}">${link}</a>
</li>
`.trim()

menuItem('https://google.com', 'Google')

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

<li>
  <a href="https://google.com">Google</a>
</li>

Интерполировать можно целые выражения, а не только переменные. Например — как здесь, где в строку встраивается результат сложения двух чисел:

const sum = (x, y) => x + y
const x = 5
const y = 100
const string = `The sum of ${x} and ${y} is ${sum(x, y)}.`

console.log(string)

Здесь объявлена функция sum() и константы x и y. После этого в строке используется и функция, и эти константы. Вот как выглядит константа string, выведенная в консоль:

The sum of 5 and 100 is 105.

Этот механизм может оказаться особенно полезным при использовании тернарного оператора [13], который позволяет проверять условия при формировании строки:

const age = 19
const message = `You can ${age < 21 ? 'not' : ''} view this page`
console.log(message)

Константа message, выведенная в консоль, может менять в зависимости от того, больше или меньше 21 значение, хранящееся в age. Так как в нашем примере это значение равняется 19, в консоль попадёт следующее:

You can not view this page

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

Теговые шаблоны

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

В следующем примере мы создаём функцию tag, которую планируем использовать в роли функции, с помощью которой выполняются операции над теговым шаблоном. Первым параметром этой функции, носящим имя strings, является массив строковых литералов. Выражения, встраиваемые в строку, помещены во второй параметр с использованием синтаксиса оставшихся параметров [14]. Для того чтобы увидеть содержимое этих параметров — их можно вывести в консоль:

function tag(strings, ...expressions) {
  console.log(strings)
  console.log(expressions)
}

Если при создании тегового шаблона воспользоваться функцией tag, то может получиться такая конструкция:

const string = tag`This is a string with ${true} and ${false} and ${100} interpolated inside.`

Так как в функции tag выполняется вывод в консоль strings и expressions, при выполнении этого кода в консоль попадёт следующее:

["This is a string with ", " and ", " and ", " interpolated inside."]
[true, false, 100]

Видно, что первый параметр, strings, это массив, содержащий все строковые литералы:

"This is a string with "
" and "
" and "
" interpolated inside."

У этого аргумента ещё есть свойство raw, к которому можно обратиться как к strings.raw. Оно содержит строку, в которой не были обработаны управляющие последовательности. Например, n будет просто символом n, а не командой перевода строки.

Второй аргумент, ...expressions, это массив, содержащий все выражения:

true
false
100

В результате оказывается, что функции тегового шаблона tag передаются строковые литералы и выражения. Обратите внимание на то, что функция не обязана возвращать строку. Она может работать с переданными ей значениями и возвращать всё что угодно. Например, у нас может быть функция, которая ни на что не обращает внимания и просто возвращает null. Именно так написана функция returnsNull в следующем примере:

function returnsNull(strings, ...expressions) {
  return null
}

const string = returnsNull`Does this work?`
console.log(string)

В результате выполнения этого кода в консоль попадёт следующее:

null

В качестве примера действия, которое можно выполнить в теговом шаблоне, можно привести внесение изменений в каждое из выражений, например, таких изменений, которые заключаются в помещении выражений в HTML-теги. Создадим функцию bold, которая добавляет теги <strong> и </strong> в начало и в конец каждого выражения:

function bold(strings, ...expressions) {
  let finalString = ''

  // Проходимся по всем выражениям
  expressions.forEach((value, i) => {
    finalString += `${strings[i]}<strong>${value}</strong>`
  })

  // Добавляем последний строковой литерал
  finalString += strings[strings.length - 1]

  return finalString
}

const string = bold`This is a string with ${true} and ${false} and ${100} interpolated inside.`

console.log(string)

Здесь, для обхода массива expressions, используется цикл forEach [15]. Каждый элемент заключается в теги <strong></strong>.

This is a string with <strong>true</strong> and <strong>false</strong> and <strong>100</strong> interpolated inside.

В популярных JavaScript-библиотеках можно найти несколько примеров использования теговых шаблонов. Так, в библиотеке graphql-tag [16] используется шаблонный литерал gql для разбора строк запроса GraphQL [17] и преобразования их в абстрактное синтаксическое дерево (abstract syntax tree, AST), понятное GraphQL:

import gql from 'graphql-tag'

// Запрос для получения имени и фамилии пользователя 5
const query = gql`
  {
    user(id: 5) {
      firstName
      lastName
    }
  }
`

Функции теговых шаблонов используются и в библиотеке styled-components [18], что позволяет создавать новые компоненты React [19] из обычных элементов DOM [20] и применять к ним дополнительные CSS-стили [21]:

import styled from 'styled-components'

const Button = styled.button`
  color: magenta;
`

// <Button> теперь может использоваться как компонент

Кроме того, можно пользоваться стандартным методом String.raw [22], применяя его к теговым шаблонам для того чтобы предотвратить обработку управляющих последовательностей:

const rawString = String.raw`I want to write /n without it being escaped.`
console.log(rawString)

В консоль после выполнения этого кода попадёт следующее:

I want to write /n without it being escaped.

Итоги

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

А вы пользуетесь шаблонными литералами?

Автор: ru_vds

Источник [23]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/355344

Ссылки в тексте:

[1] ES6: http://www.ecma-international.org/ecma-262/6.0/

[2] строковых значений: https://www.digitalocean.com/community/tutorials/how-to-work-with-strings-in-javascript

[3] URL: https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_URL

[4] HTML-элементов: https://developer.mozilla.org/en-US/docs/Web/HTML

[5] Image: https://habr.com/ru/company/ruvds/blog/511590/

[6] экранировать: https://www.digitalocean.com/community/tutorials/how-to-work-with-strings-in-javascript#escaping-quotes-and-apostrophes-in-strings

[7] консоль: https://www.digitalocean.com/community/tutorials/how-to-use-the-javascript-developer-console

[8] оператор конкатенации строк: https://www.digitalocean.com/community/tutorials/how-to-work-with-strings-in-javascript#string-concatenation

[9] функция: https://www.digitalocean.com/community/tutorials/how-to-define-functions-in-javascript

[10] OAuth: https://tools.ietf.org/html/rfc6749

[11] trim(): https://www.digitalocean.com/community/tutorials/how-to-index-split-and-manipulate-strings-in-javascript#trimming-whitespace

[12] с настраиваемой ссылкой используется стрелочная функция: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li

[13] тернарного оператора: https://www.digitalocean.com/community/tutorials/how-to-write-conditional-statements-in-javascript#ternary-operator

[14] синтаксиса оставшихся параметров: https://www.digitalocean.com/community/tutorials/understanding-destructuring-rest-parameters-and-spread-syntax-in-javascript

[15] forEach: https://www.digitalocean.com/community/tutorials/how-to-use-array-methods-in-javascript-iteration-methods#foreach()

[16] graphql-tag: https://github.com/apollographql/graphql-tag

[17] GraphQL: https://graphql.org/

[18] styled-components: https://github.com/styled-components/styled-components

[19] компоненты React: https://www.digitalocean.com/community/tutorials/how-to-create-custom-components-in-react

[20] DOM: https://www.digitalocean.com/community/tutorials/introduction-to-the-dom

[21] CSS-стили: https://developer.mozilla.org/en-US/docs/Web/CSS

[22] String.raw: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw

[23] Источник: https://habr.com/ru/post/511590/?utm_source=habrahabr&utm_medium=rss&utm_campaign=511590