Как я с template и slot подружиться пытался

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Рабочее место веб-разработчика
Рабочее место веб‑разработчика

Я занимаюсь веб‑разработкой и пишу на чистом JavaScript, и PHP уже больше 20 лет. За это время я пережил jQuery, Backbone, Ember, AngularJS и видел, как React и Vue захватили умы джунов. Долгое время я искренне считал, что <template> и <slot> — это какая‑то странная ерунда штука для полифиллов Web Components, которая в реальной жизни не нужна от слова «совсем».

Зачем клонировать узлы, если есть innerHTML? Зачем Shadow DOM, если есть BEM?

Оказалось, что я просто боялся нативных инструментов. Ага. Но вот, наконец‑то, перестал и моя жизнь стала проще, а бандлы легче.


Что такое template и почему innerHTML — это боль

Представь, что тебе нужно отрендерить список из 100 карточек товаров. Классический подход 2015 года был бы таким: собрать строку в цикле и воткнуть её в контейнер.

const data = [{name: 'Кеды', price: 3000}, {name: 'Футболка', price: 1500}];
let html = '';

data.forEach(item => {
  html += '<div class="card"><h3>${item.name}</h3><p>${item.price} ₽</p></div>';
});

document.getElementById('list').innerHTML = html;

Проблем здесь три. Во‑первых, парсер браузера каждый раз при innerHTML вынужден заново разбирать эту строку, создавать узлы и вешать их в дерево. Во‑вторых, если в строке есть изображения, браузер попытается их загрузить прямо в момент, даже если карточки скрыты. В‑третьих, это просто уродливо.

<template> решает это. Браузер видит этот тег, понимает, что это болванка, и не рендерит её содержимое. Скрипты внутри не выполняются, картинки не грузятся, стили не применяются. Это просто инертный кусок DOM‑дерева.

<template id="card-template">
  <div class="card">
    <h3 class="card__title"></h3>
    <p class="card__price"></p>
  </div>
</template>
const template = document.getElementById('card-template');
const container = document.getElementById('list');
const fragment = document.createDocumentFragment();

data.forEach(item => {
  const node = template.content.cloneNode(true); // true — клонируем рекурсивно
  node.querySelector('.card__title').textContent = item.name;
  node.querySelector('.card__price').textContent = item.price + ' ₽';
  fragment.appendChild(node);
});

container.appendChild(fragment);

Мы клонируем готовую структуру. Парсер не напрягается, картинки не едят трафик, а DocumentFragment позволяет закинуть все 100 карточек в DOM за один рефлоу.

Что такое slot и почему это не просто плейсхолдер

Долгое время я думал, что <slot> — это просто именованный div внутри веб‑компонента. Но когда потрогал его руками сам, то понял, что это не так. <slot> — это механизм проекции контента.

Работает он только внутри Shadow DOM.

Shadow DOM — это параллельная вселенная внутри твоего тега. Стили оттуда не утекают наружу, стили снаружи не пробиваются внутрь.

А <slot> — это единственный легальный мостик, через который пользователь твоего компонента может протолкнуть свой HTML внутрь твоей инкапсуляции.

Для примера, сделаем переиспользуемый компонент карточки, где заголовок и подвал кастомизирует потребитель, а рамку и цену — мы.

<!-- Это мы пишем в HTML страницы -->
<my-card>
  <h2 slot="header">Лимитированные кроссовки</h2>
  <p slot="footer">Доставка завтра</p>
</my-card>

<script>
    class MyCard extends HTMLElement {
    constructor() {

        super();
        const shadow = this.attachShadow({ mode: 'open' });

        shadow.innerHTML = `
    
          <style>
            .wrapper { border: 1px solid #ccc; padding: 16px; border-radius: 8px; }
            ...
      </style>

      <div class="wrapper">
          <slot name="header">[Заголовок по умолчанию]</slot>
          <slot>Это основной контент, если слоты не заданы</slot>
          <slot name="footer"></slot>
      </div>
    `;
  }
}

customElements.define('my-card', MyCard);

</script>

В итоге, браузер берет h2 из обычного DOM, видит атрибут slot="header" и физически перемещает этот узел внутрь Shadow DOM.

Важные моменты, которые я бы запомнил

  • Если ты не укажешь slot="...", контент упадет в безымянный (дефолтный) <slot>.

  • Стили внутри Shadow DOM не видят содержимое слотов. Если ты хочешь стилизовать то, что пользователь воткнул в слот, используй CSS‑псевдокласс ::slotted(.my-class). Но учти, он работает только с прямым потомком, а не с глубокой вложенностью.

Варианты использования

Я перестал тащить React и иже с ним, в проекты, где он не нужен, и теперь использую эту связку везде.

Во‑первых, микро‑виджеты на чужих сайтах. Мне нужно было встроить калькулятор доставки в старый сайт на Bitrix. Я завернул логику в Web Component. Shadow DOM гарантировал, что глобальные стили Bitrix (их ужасные!important на каждый класс) не сломают мой калькулятор, а <slot> позволил владельцу сайта вставить внутрь виджета свой баннер.

Во‑вторых, модальные окна без зоопарка z-index. Модалка в Shadow DOM живет в своем слое. Тебе не нужно бояться, что какой‑то плагин слайдера перекроет твое окно своим z-index: 100500.

В‑третьих, динамические формы. У меня есть конструктор анкет. Шаблон вопроса (текстовый, выбор, дата) лежит в <template>. Когда пользователь добавляет вопрос, я клонирую нужный шаблон, пробрасываю данные и цепляю делегирование событий на общий контейнер.

Ну и серверный рендеринг (SSR). Я отдаю с бэкенда голый <my-card></my-card> с пустыми слотами. Пользователь видит мгновенную болванку, а JS на клиенте уже подтягивает данные и заполняет слоты через textContent. Никаких морганий верстки.

Ну и к чему, в итоге, все это привело

Задача сделать в панели управления список пуш‑уведомлений. Список обновляется раз в 5 секунд, сообщения могут добавляться, удаляться, их может быть сотни. Раньше я бы написал класс на JS, который собирает строки.

А сейчас, я сделал иначе. На бэкенде (PHP) я генерирую только JSON с данными. В HTML страницы лежит один‑единственный <template id="notif-item">.

<template id="notif-item">
  <li class="notif" data-id="">
    <slot name="icon"><span class="icon-default">						
Источник: https://habr.com/ru/articles/1098408/


Интересные статьи

Интересные статьи

Всем привет, меня зовут Никита и я CEO VSL-BI. Мы с командой занимаемся внедрением BI-аналитики и автоматизацией сбора данных в компаниях разного масштаба.В большинстве случаев к нам приходят с задача...
Представляю вашему вниманию цикл статей по работе с пока еще мало знакомым многим битрикс-разработчикам инструментом оперирования данными с CRM Битрикс24 через абстрактные фабрики. В первой статье рас...
Введение санкций против России и, как следствие, отказ компании TSMC исполнять заказы отечественных дизайн-центров, привёли к ситуации, когда вопрос «где и как производить отечественные чипы» стал реб...
У нас возникла задача добавить препроцессинг для параметров активити бизнес-процесса Битрикс24. Когда разбирались в задаче не смогли найти ни одного примера и решили выложить свой - может быть кто-ниб...
Сегодня в нашем телеграм-канале Funscience вышел перевод интересной статьи Universe Today о Бетельгейзе . Бетельгейзе —  десятая по яркости звезда на ночном небе, вторая по яркост...