Вы когда-нибудь задумывались над тем, как волшебным образом работает синтаксический сахар, например styled? Давайте воссоздадим эту функцию, используя помеченные строки шаблона в Javascript.

Если вы использовали такие инструменты, как styled-components или graphql, вы, скорее всего, сталкивались с теговыми литералами шаблонов. Новая функция Javascript, представленная в ES6.

В стилизованных компонентах шаблонные строки позволяют создавать компоненты почти волшебным образом:

const Title = styled.h1`
  font-size: ${props => props.theme.sizes.lg};
  text-align: center;
  color: palevioletred;
`;

В этом примере styled.h1 — это функция шаблона или тег. Следующая строка представляет фактический шаблон. Мы называем это строка шаблона. Соединив обе части, мы получим волшебство 🪄

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

const output = `My name is ${name}.`;

… и динамически заполнить значение name.

Функция шаблона styled.h1 используется для маркировки строки, отсюда и название помеченных литералов шаблона. Это позволяет нам сообщить движку, как строка с тегами должна быть заполнена и построена.

Учитывая простой пример печати My name is ${name}, браузер обычно просто вставляет текущее значение name в строку.

Но, может быть, мы хотим сделать что-то другое со строкой? Запустив функцию шаблона для строки, мы можем сделать именно это и контролировать, как будет выглядеть вывод.

Функция шаблона получает всю информацию о строке шаблона (ее частях и переменных), и все, что мы возвращаем из функции, будет нашим output.

Понимание функции шаблона

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

В этом простом примере ниже мы распечатываем краткое введение, используя тег introduce.

function introduce(strings, name, food) {
  // construct and return string
}
const name = 'Nina';
const food = '🍕';
const output = introduce`I am ${name}. I like ${food}.`;

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

strings содержит массив строк, практически все статические части между динамическими переменными. В нашем случае это массив, содержащий 3 элемента: I am, . I like и ..

Все последующие аргументы являются значениями наших интерполированных переменных, name и food.

Используя оператор rest ES6, мы можем объединить эти последующие аргументы следующим образом:

function introduce(strings, ...args) {
  // construct and return string
}

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

Создание собственного тега `styled`

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

const Title = styled.h1`
  font-size: ${props => props.theme.sizes.lg};
  text-align: center;
  color: palevioletred;
`;

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

Для стиля мы используем тему, к которой мы обращаемся внутри нашей строки шаблона. Таким образом, компонент Title должен учитывать размеры шрифта, которые мы внедряем через нашу тему.

Хотя это может показаться сложным, это довольно просто реализовать!

Во-первых, давайте создадим некоторые предпосылки:

// This acts as our theme provider
const props = {
  theme: {
    sizes: {
      xl: '2rem',
      lg: '1.5rem',
      md: '1rem',
      xs: '0.75rem',
    },
  },
};
// This will be our template function
function h1(strings, ...args) {
  // Do something
}
const styled = { h1 };
// This will be our styled component
const Title = styled.h1`
  font-size: ${props => props.theme.sizes.lg};
  text-align: center;
  color: palevioletred;
`;

Для простоты мы предполагаем, что можем получить доступ к глобальному объекту props, который действует для нас как поставщик темы.

Ниже нашего объекта props мы определяем нашу шаблонную функцию h1. Чтобы сделать его еще более похожим на стилизованные компоненты, я обернул функцию в объект с именем styled, который мы затем используем для создания нашего компонента Title.

Давайте подробнее рассмотрим функцию h1 и то, как она используется:

function h1(strings, ...args) {
  // Do something
}
const Title = styled.h1`
  font-size: ${props => props.theme.sizes.lg};
  text-align: center;
  color: palevioletred;
`;

Вспоминая то, что мы узнали ранее, аргумент strings содержит все части статической строки нашего шаблона. В данном случае у него всего два элемента: font-size: и ; text-align: center; ….

args содержит интерполированные значения. Обратите внимание, что в этом примере мы на самом деле интерполируем функции, а не только строки, как мы делали раньше. Он работает так же, но предоставляет гораздо больше возможностей, как мы увидим через секунду!

Чтобы создать помеченный литерал шаблона, который выводит такой стилизованный компонент, нам нужно сделать две вещи:

  1. Создание стилей на основе темы на основе предоставленной строки шаблона
  2. Создание и возврат компонента React с этими стилями

Итак, давайте сделаем именно это и начнем с шага один.

Создание стилей на основе темы

На этом первом шаге мы создаем стиль, который позже применим к нашему компоненту. На данный момент мы можем вывести стиль в виде простой строки CSS, аналогично текущему формату в строке шаблона.

const props = {
  theme: {
    sizes: {
      xl: '2rem',
      lg: '1.5rem',
      md: '1rem',
      xs: '0.75rem',
    },
  },
};
function h1(strings, ...args) {
  let str = '';
  strings.forEach((string, i) => {
    str += string + (args[i] ? args[i](props) : '');
  });
  return str;
}
const styles = h1`
  font-size: ${props => props.theme.sizes.lg};
  text-align: center;
  color: palevioletred;
`;

В нашей функции h1 мы перебираем части статической строки шаблона и постепенно объединяем их. Используя индекс i, мы можем проверить для каждой части, есть ли связанное интерполированное значение.

Итак, после присоединения font-size: к исходной пустой строке мы проверяем, существует ли интерполированное значение с индексом 0. И действительно, оно есть! Наша функция, которую мы предоставили для определения размера шрифта на основе темы.

Поскольку args[i] содержит введенное нами значение (props) => props.theme.sizes.lg, мы можем вызвать эту функцию с глобальным объектом props и получить обратно размер шрифта. Идеальный!

Наконец, мы возвращаем str из нашей функции шаблона. Он содержит строку CSS, подобную этой:

font-size: 1.5rem;
text-align: center;
color: palevioletred;

Возврат стилизованного компонента React

Чтобы полностью эмулировать стилизованные компоненты, теперь мы хотим вернуть стилизованный компонент React вместо строки CSS.

Мы можем сделать это легко, используя небольшую вспомогательную функцию css2obj, которая преобразует строку CSS в объект CSS, который можно применить к компоненту React. Это не совсем то, как работают стилизованные компоненты, но на данный момент это работает. Вы можете проверить реализацию здесь, если вам интересно.

function h1(strings, ...args) {
  let str = '';
  strings.forEach((string, i) => {
    str += string + (args[i] ? args[i](props) : '');
  });
  return React.createElement('h1', {
    style: css2obj(str),
  });
}

Благодаря этому мы можем адаптировать нашу реализацию h1 так, чтобы мы не возвращали строку CSS, а создавали элемент React с правильным типом заголовка и сгенерированным объектом стиля.

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

const Title = styled.h1`
  font-size: ${props => props.theme.sizes.lg};
  text-align: center;
  color: palevioletred;
`;
export const MyComponent = () => {
  return <Title>My Heading</Title>;
};

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

Для меня это было одной из вещей, которые всегда выглядели (и звучали) намного сложнее, чем они есть на самом деле.

Итак, чтобы вы помнили:

Тегированные литералы шаблонов позволяют определить, как должна создаваться и возвращаться строка шаблона.

Еще одна приятная техника для пояса с инструментами! Если вам интересно, вы найдете полный пример в этой CodeSandbox.

Эта статья была впервые опубликована на konstantin.digital/blog. Я ежемесячно пишу о веб-разработке и разработке продуктов с помощью React, а также о том, как стать лучшим разработчиком.

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord . Заинтересованы в хакинге роста? Ознакомьтесь с разделом Схема.