Skip to main content

Шаблонні рядки в JavaScript

Шаблонні рядки (template literals) - це рядки у зворотних лапках (`), які дозволяють вставляти вирази через синтаксис ${}, писати багаторядковий текст без escape-послідовностей і використовувати теговані шаблони. З'явились в ES6.

Теорія

TL;DR

  • Шаблонні рядки - як mail merge: пишеш шаблон один раз, вставляєш дані де треба
  • Головна різниця: зворотні лапки + ${} замість конкатенації через +
  • Є змінні або багаторядковий текст - бери шаблонний рядок
  • Статичний текст без змінних - звичайні лапки читаються простіше
  • В ${} можна писати будь-який вираз: математику, виклики функцій, тернарний оператор

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

javascript
const name = "Alice"; const age = 28; // Старий спосіб const old = "Hello, " + name + "! You are " + age + " years old."; // Шаблонний рядок const modern = `Hello, ${name}! You are ${age} years old.`; // "Hello, Alice! You are 28 years old." // Багаторядковий текст без \n const message = `Welcome, ${name}! Your account is active. Age: ${age}`;

Синтаксис із зворотними лапками прибирає потребу в + між кожним шматком рядка і змінною.

Головна різниця

Шаблонні рядки обчислюють вирази в ${} під час виконання і автоматично перетворюють результат на рядок. При конкатенації треба ланцюжком з'єднувати шматки через +, а для переносів рядка додавати \n. Шаблонні рядки вирішують обидві проблеми одразу. І тільки вони відкривають теговані шаблони (tagged templates) - коли функція отримує статичні частини рядка і значення виразів окремо, до того як рядок зібрано. Зі звичайними лапками це неможливо.

Коли використовувати

  • Будь-який рядок де є змінні
  • Багаторядковий контент: HTML-фрагменти, SQL-запити, повідомлення про помилки
  • Складні вирази: ${user.isAdmin ? 'admin' : 'user'}
  • Динамічні атрибути і class-імена в компонентах
  • Уникай для статичних рядків без змінних - там звичайні лапки читаються краще

На практиці, коли звикаєш до шаблонних рядків, конкатенація починає дратувати навіть у простих двочастинних рядках.

Як це працює всередині

Коли JS-рушій зустрічає шаблонний рядок, він розбирає текст між зворотними лапками і знаходить кожен блок ${}. Кожен вираз обчислюється в поточному scope, перетворюється через .toString() і вставляється на своє місце. З тегованими шаблонами рушій розділяє статичні частини рядка і значення виразів на окремі аргументи, передає їх у тег-функцію - і вже функція вирішує як їх об'єднати. Структура розбирається під час парсингу, обчислення виразів відбувається під час виконання.

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

Забули $ перед {}

javascript
const name = "Alice"; // ❌ Виведе літеральний текст const greeting = `Hello, {name}!`; console.log(greeting); // "Hello, {name}!" - фігурні дужки без $ нічого не роблять // ✅ Правильно const greeting2 = `Hello, ${name}!`; console.log(greeting2); // "Hello, Alice!"

Шаблонний рядок як ключ об'єкта

javascript
const key = "user"; // ❌ Ключ стає літеральним рядком "${key}_name" // Без квадратних дужок: { '${key}_name': 'Alice' } - неправильно // ✅ Обчислювані властивості з квадратними дужками const obj = { [`${key}_name`]: "Alice" }; console.log(obj); // { user_name: 'Alice' }

Небезпечний ввід користувача і XSS

javascript
const userInput = "<img src=x onerror='alert(1)'>"; // ❌ Скрипт виконається при рендері const html = `<div>${userInput}</div>`; // ✅ Екрануй перед вставкою function escapeHtml(text) { const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }; return text.replace(/[&<>"']/g, m => map[m]); } const safeHtml = `<div>${escapeHtml(userInput)}</div>`; // <div>&lt;img src=x onerror='alert(1)'&gt;</div> - безпечно

Зайві пробіли і переноси в багаторядкових шаблонах

javascript
// ❌ Перед <div> буде перенос рядка і відступи const html = ` <div> <p>Content</p> </div> `; // ✅ Використовуй .trim() або уникай початкового переносу const html2 = `<div> <p>Content</p> </div>`.trim();

Де зустрічається в реальних проектах

  • React: динамічні className і aria-label атрибути
  • Express/Node.js: SQL-запити, log-повідомлення, відповіді з помилками
  • Apollo Client: GraphQL-запити через тег gql
  • styled-components: CSS-in-JS через синтаксис тегованих шаблонів
  • Jest: опис тестів і snapshot-рядки

Питання на співбесіді

Q: Що буде якщо вставити об'єкт у ${}?
A: Викличеться .toString(), і ти отримаєш [object Object]. Якщо потрібен читабельний вивід - використовуй JSON.stringify(obj).

Q: Чи можна використовувати шаблонний рядок як ключ об'єкта?
A: Напряму - ні. Без квадратних дужок зворотні лапки розглядаються як літеральний ключ. Потрібен синтаксис обчислюваних властивостей: { [\key_${x}`]: value }`.

Q: Як працюють теговані шаблони (tagged templates)?
A: Тег - це функція що отримує масив статичних частин рядка і значення виразів як окремі аргументи: (strings, ...values). Функція сама вирішує як їх об'єднати. Саме так sql-теги екранують параметри запитів, а styled-components обробляє CSS-рядки.

Q: Чи повільніші шаблонні рядки порівняно з конкатенацією?
A: Ні. Сучасні рушії V8 і SpiderMonkey оптимізують обидва підходи. Різниця в продуктивності несуттєва. Причина обирати шаблонні рядки - читабельність.

Q: (Senior-рівень) Як написати тегований шаблон що автоматично захищає від XSS?
A: Пиши тег-функцію яка проходить по масиву values і екранує кожне значення перед вставкою. Функція отримує (strings, ...values) - пройди по values з функцією екранування і чергуй з елементами strings. Саме так працюють бібліотеки lit-html і htm.

Приклади

Базова інтерполяція і багаторядковий рядок

javascript
const user = { name: "Alice", email: "alice@example.com" }; const isActive = true; const card = ` Name: ${user.name} Email: ${user.email} Status: ${isActive ? "active" : "inactive"} Joined: ${new Date().toLocaleDateString()} `.trim(); console.log(card); // Name: Alice // Email: alice@example.com // Status: active // Joined: 15.01.2025

Тернарні оператори, виклики методів і звернення до властивостей - все це працює всередині ${}. .trim() на кінці прибирає початковий і кінцевий переноси рядка від зворотних лапок.

Тегований шаблон для безпечних SQL-запитів

javascript
function sql(strings, ...values) { const escaped = values.map(v => typeof v === "string" ? `'${v.replace(/'/g, "''")}'` : v ); let result = strings[0]; for (let i = 0; i < escaped.length; i++) { result += escaped[i] + strings[i + 1]; } return result; } const userId = 42; const userName = "O'Brien"; // містить апостроф const query = sql`SELECT * FROM users WHERE id = ${userId} AND name = ${userName}`; console.log(query); // SELECT * FROM users WHERE id = 42 AND name = 'O''Brien' // Апостроф екранований, SQL-ін'єкція неможлива

Тег-функція запускається до того як рядок збирається. Вона отримує статичні частини в strings і обчислені вирази в values. Це той самий патерн що використовує Apollo Client для GraphQL-запитів через тег gql.

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

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

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

Дочитали статтю?