Skip to main content

Теговані шаблонні рядки

Тегований шаблонний рядок, це шаблонний рядок, перед яким записано ім'я функції; замість автоматичного склеювання JavaScript викликає цю функцію і передає їй частини рядка та підставлені значення окремо. Тег сам вирішує, що робити зі значеннями, тому таку конструкцію використовують для екранування, форматування та власних міні-мов на кшталт CSS-in-JS.

Теорія

TL;DR

  • Звичайний шаблон просто вставляє значення, тегований віддає контроль функції.
  • Функція-тег отримує два набори даних: масив текстових частин і список підставлених значень.
  • tag`Hello, ${name}!` виконується як tag(['Hello, ', '!'], name).
  • Повертати тег може будь-що, не обов'язково рядок.
  • Головні застосування: безпечний HTML, CSS-in-JS, GraphQL-запити, форматування, i18n.

Швидкий приклад

javascript
function tag(strings, ...values) { console.log(strings); // [ 'Hello, ', '!' ] console.log(values); // [ 'Alex' ] } const name = 'Alex'; tag`Hello, ${name}!`;

Що відбувається під капотом:

javascript
// Інтерпретатор робить приблизно це: tag(['Hello, ', '!'], 'Alex');

Як це виглядає і що отримує тег

Звичайний шаблонний рядок просто підставляє змінні:

javascript
const name = 'Alex'; console.log(`Hello, ${name}!`); // Hello, Alex!

Але якщо перед шаблонним рядком вказати ім'я функції, JavaScript викличе цю функцію і передасть у неї:

  1. Масив частин рядка (тексту між підстановками).
  2. Усі підставлені значення з ${...}.
javascript
tag`Hello, ${name}!`

Це і є тегований шаблон. Отже:

  • strings, це масив усіх шматків тексту між вставками;
  • values, це масив усіх вставлених значень, зібраний rest-параметром.

Текстових частин завжди на одну більше, ніж значень, бо шаблон починається і закінчується текстом (можливо, порожнім).

Обробка вмісту: власний шаблонізатор

Тег-функцію можна використати для створення власних шаблонних мов або форматування рядків.

javascript
function highlight(strings, ...values) { return strings.reduce( (result, str, i) => result + str + (values[i] ? `<b>${values[i]}</b>` : ''), '' ); } const name = 'Alex'; const lang = 'JavaScript'; const message = highlight`Hello, ${name}! You love ${lang}.`; console.log(message); // Hello, <b>Alex</b>! You love <b>JavaScript</b>.

Тут ми «зловили» кожну вставку ${...} і загорнули її у <b>...</b>.

Так само легко зробити форматувальник:

javascript
function format(strings, ...values) { return strings.reduce( (res, str, i) => res + str + (values[i] !== undefined ? `[${values[i]}]` : ''), '' ); } const a = 10, b = 20; console.log(format`Sum of ${a} and ${b} is ${a + b}`); // Sum of [10] and [20] is [30]

Захист від XSS: безпечний HTML

Найкорисніший сценарій: тег екранує все, що прийшло ззовні, тому розмітка лишається безпечною.

javascript
function safeHTML(strings, ...values) { const escape = str => String(str) .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;'); return strings.reduce( (res, str, i) => res + str + (values[i] ? escape(values[i]) : ''), '' ); } const userInput = '<script>alert("Hacked!")</script>'; const html = safeHTML`<div>User says: ${userInput}</div>`; console.log(html); // <div>User says: &lt;script&gt;alert("Hacked!")&lt;/script&gt;</div>

Завдяки тегу HTML лишається безпечним, ніякого виконання стороннього скрипта. Важливо, що екрануються саме значення, а статичний текст шаблону, який написав розробник, лишається недоторканим.

Реальні застосування

  1. Styled Components (React)

    javascript
    const Button = styled.button` background: ${props => props.primary ? 'blue' : 'gray'}; color: white; `;

    Тут styled.button, це тег-функція, яка отримує шаблон і будує з нього CSS.

  2. GraphQL-запити

    javascript
    const query = gql` query GetUser { user(id: 1) { name } } `;

    Тег gql парсить текст запиту в AST ще до відправлення на сервер.

  3. Інтернаціоналізація (i18n)

    javascript
    translate`Welcome, ${userName}!`;

    Тег знаходить потрібний переклад за статичною частиною і підставляє значення в нього.

Порівняння зі звичайним шаблонним рядком

ОсобливістьЗвичайний шаблонний рядокТегований шаблонний рядок
Синтаксис`Hello, ${name}`tag`Hello, ${name}`
ПідстановкиВставляються автоматичноОбробляються вручну в тегу
ГнучкістьЛише інтерполяціяМожна фільтрувати, форматувати, екранувати
РезультатЗавжди рядокБудь-яке значення, яке поверне тег
ЗастосуванняПрості рядкиШаблонізація, безпека, CSS-in-JS, SQL, i18n

Підсумок:

ПитанняВідповідь
Що це такеШаблонний рядок, перед яким стоїть функція-тег, що отримує частини рядка і значення
Навіщо потрібноЩоб керувати тим, як з'єднуються та інтерпретуються вставки ${}
Де використовуєтьсяБезпечний HTML, CSS-in-JS, форматування, інтернаціоналізація
Ключові параметриstrings (частини шаблону), values (вставлені значення)

Типові помилки

  1. Ставити дужки після тега. tag(Hello ${name}) це звичайний виклик функції з уже склеєним рядком; тегований шаблон пишеться без дужок, одразу за іменем функції.
  2. Очікувати, що тег отримає готовий рядок. Перший аргумент, це масив частин, решта приходить через rest; склеювати їх має сама функція.
  3. Плутати кількість елементів. У strings завжди на один елемент більше, ніж у values, тому наївний цикл по values губить останній шматок тексту.
  4. Екранувати не те. Екранувати треба значення, а не весь результат: якщо прогнати через екранування статичний HTML шаблону, розмітка перетвориться на текст.
  5. Забувати про strings.raw. Звичайний масив містить оброблені escape-послідовності, а strings.raw дає сирий текст; саме на цьому побудований String.raw.
  6. Вважати, що тег зобов'язаний повернути рядок. Тег може повернути об'єкт, функцію або AST, як це роблять gql і CSS-in-JS бібліотеки.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.