Tagged Template Literals

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

Зачем нужно

Tagged templates дают возможность обрабатывать шаблонные строки как DSL: экранировать HTML, форматировать SQL, переводить строки, стилизовать компоненты. Это позволяет строить безопасные и выразительные API прямо в JavaScript-синтаксисе.

Где используется

  • Экранирование HTML (защита от XSS)
  • Параметризованные SQL-запросы (защита от SQL injection)
  • CSS-in-JS (styled-components используют tagged templates)
  • Интернационализация и форматирование строк
  • GraphQL-запросы (библиотека gql)

Основной контент

Базовый синтаксис

// Тег — функция, принимающая (strings, ...values())
function tag(strings, ...values()) {
  console.log(strings); // массив строковых частей (raw)
  console.log(values);  // массив вычисленных выражений
  return 'результат'; // тег может вернуть что угодно
}

const name = 'Мир';
const count = 42;
tag`Привет, ${name}! Ответ: ${count}.`;
// strings: ['Привет, ', '! Ответ: ', '.']
// values: ['Мир', 42]

Пример: HTML-экранирование

function html(strings, ...values()) {
  const escape = (v) => String(v)
    .replace(/&/g, '&')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');

  return strings.reduce((result, str, i) => {
    const value = values[i - 1];
    return result + escape(value) + str;
  });
}

const userInput = '<script>alert("XSS")</script>';
const safeHtml = html`<p>Пользователь написал: ${userInput}</p>`;
console.log(safeHtml);
// <p>Пользователь написал: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;</p>

Пример: SQL параметризация

function sql(strings, ...values()) {
  const query = strings.reduce((q, str, i) => {
    return q + (i > 0 ? `$${i}` : '') + str;
  });
  return { query, params: values };
}

const userId = 42;
const status = 'active';
const { query, params } = sql`
  SELECT * FROM users
  WHERE id = ${userId} AND status = ${status}
`;
// query: 'SELECT * FROM users WHERE id = $1 AND status = $2'
// params: [42, 'active']
// Безопасно от SQL injection!

Styled-components стиль

// Упрощённая имитация styled-components
function styled(tag) {
  return (strings, ...values()) => {
    const css = strings.reduce((result, str, i) => {
      const value = typeof values[i] === 'function'
        ? values[i]({ theme: 'light' }) // props/theme injection
        : (values[i] ?? '');
      return result + str + value;
    }, '');

    const className = `sc-${Math.random.toString(36).slice(2, 8)}`;
    const style = document.createElement('style');
    style.textContent = `.${className} { ${css} }`;
    document.head.appendChild(style);

    return `${tag}.${className}`;
  };
}

const Button = styled('button')`
  background: ${props => props.primary ? 'blue' : 'white'};
  padding: 8px 16px;
  border-radius: 4px;
`;

strings.raw — необработанные строки

// String.raw — встроенный тег, возвращает строку без обработки escape
const path = String.raw`C:\Users\name\Documents`;
console.log(path); // 'C:\Users\name\Documents' (не \U, \n, \D)

// strings.raw в кастомном теге
function debug(strings, ...values()) {
  return strings.raw.reduce((result, str, i) => {
    return result + (values[i - 1] ?? '') + str;
  });
}

const n = 10;
console.log(debug`Значение: ${n}\nСледующая строка`);
// 'Значение: 10\nСледующая строка' (литеральный \n, не перенос)

Частые ошибки

  • strings.length === values.length + 1 — строковых частей всегда на одну больше чем значений. Не забывайте обрабатывать первую часть (strings[0]) и последнюю (strings[strings.length - 1]).
  • Возврат не строки из тега — тег может вернуть любой тип: массив, объект, null. TypeScript и другие инструменты могут этого не ожидать.
  • Пропуск strings.raw при работе с путями — обычный strings[i] обрабатывает escape-последовательности (\n → перенос строки). Для путей используйте strings.raw[i].

Связанные темы

Ресурсы


⚡ Источник: JavaScript template literal или 100 плюс 1 способ вызвать функцию · AsForJS

  • 📅 2023-05-29 · YouTube
  • Тезисы:
    • Tagged template — это форма function call: fn\hello ${x}`fn(['hello ', ''], x)`
    • Это альтернативная грамматика вызова — без скобок, с особыми аргументами
    • strings.raw хранит исходный текст без обработки escape (для регексов, путей, latex)
    • Используется в styled-components, GraphQL tag, lit-html, sql tagged templates

🎓 Источник: Work with strings, templates and Unicode

  • 📅 2018-10-18 · YouTube
  • Тезисы: template literals — это синтаксический сахар с двумя режимами: untagged (просто интерполяция) и tagged (полный контроль). Это базис для построения eDSL в JS