Шаблонні рядки в JavaScript
Шаблонні рядки (template literals) - це рядки у зворотних лапках (`), які дозволяють вставляти вирази через синтаксис ${}, писати багаторядковий текст без escape-послідовностей і використовувати теговані шаблони. З'явились в ES6.
Теорія
TL;DR
- Шаблонні рядки - як mail merge: пишеш шаблон один раз, вставляєш дані де треба
- Головна різниця: зворотні лапки +
${}замість конкатенації через+ - Є змінні або багаторядковий текст - бери шаблонний рядок
- Статичний текст без змінних - звичайні лапки читаються простіше
- В
${}можна писати будь-який вираз: математику, виклики функцій, тернарний оператор
Швидкий приклад
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() і вставляється на своє місце. З тегованими шаблонами рушій розділяє статичні частини рядка і значення виразів на окремі аргументи, передає їх у тег-функцію - і вже функція вирішує як їх об'єднати. Структура розбирається під час парсингу, обчислення виразів відбувається під час виконання.
Типові помилки
Забули $ перед {}
const name = "Alice";
// ❌ Виведе літеральний текст
const greeting = `Hello, {name}!`;
console.log(greeting); // "Hello, {name}!" - фігурні дужки без $ нічого не роблять
// ✅ Правильно
const greeting2 = `Hello, ${name}!`;
console.log(greeting2); // "Hello, Alice!"Шаблонний рядок як ключ об'єкта
const key = "user";
// ❌ Ключ стає літеральним рядком "${key}_name"
// Без квадратних дужок: { '${key}_name': 'Alice' } - неправильно
// ✅ Обчислювані властивості з квадратними дужками
const obj = { [`${key}_name`]: "Alice" };
console.log(obj); // { user_name: 'Alice' }Небезпечний ввід користувача і XSS
const userInput = "<img src=x onerror='alert(1)'>";
// ❌ Скрипт виконається при рендері
const html = `<div>${userInput}</div>`;
// ✅ Екрануй перед вставкою
function escapeHtml(text) {
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
return text.replace(/[&<>"']/g, m => map[m]);
}
const safeHtml = `<div>${escapeHtml(userInput)}</div>`;
// <div><img src=x onerror='alert(1)'></div> - безпечноЗайві пробіли і переноси в багаторядкових шаблонах
// ❌ Перед <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.
Приклади
Базова інтерполяція і багаторядковий рядок
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-запитів
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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.