← Назад к вопросам

Как применять Slot в Web компоненте?

2.2 Middle🔥 71 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Slot в Web Components

Slot - это мощный механизм для создания переиспользуемых компонентов с гибкой структурой. Расскажу как его использовать.

Что такое Slot

Slot позволяет компоненту принимать содержимое от родителя и размещать его в определённых местах:

// Простой Web Component с default slot
class Card extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        .card {
          border: 1px solid #ccc;
          padding: 16px;
          border-radius: 8px;
          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
      </style>
      <div class="card">
        <slot></slot>
      </div>
    `;
  }
}

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

// Использование
// HTML:
// <my-card>
//   <h1>Заголовок</h1>
//   <p>Содержимое карточки</p>
// </my-card>

Default Slot

Default slot принимает всё содержимое компонента:

class Button extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        button {
          padding: 10px 20px;
          background: #007bff;
          color: white;
          border: none;
          border-radius: 4px;
          cursor: pointer;
        }
        button:hover {
          background: #0056b3;
        }
      </style>
      <button>
        <slot></slot>
      </button>
    `;
  }
}

customElements.define('my-button', Button);

// Использование:
// <my-button>Click me</my-button>
// <my-button><strong>Bold text</strong></my-button>

Named Slots

Named slots позволяют различать разные области содержимого:

class Modal extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        .modal {
          position: fixed;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
          background: white;
          padding: 20px;
          border-radius: 8px;
          box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
          z-index: 1000;
          max-width: 500px;
        }
        .header {
          border-bottom: 1px solid #ddd;
          padding-bottom: 10px;
          margin-bottom: 10px;
        }
        .body {
          margin: 15px 0;
        }
        .footer {
          border-top: 1px solid #ddd;
          padding-top: 10px;
          margin-top: 10px;
          text-align: right;
        }
      </style>
      <div class="modal">
        <div class="header">
          <slot name="header"></slot>
        </div>
        <div class="body">
          <slot></slot>
        </div>
        <div class="footer">
          <slot name="footer"></slot>
        </div>
      </div>
    `;
  }
}

customElements.define('my-modal', Modal);

// Использование:
// <my-modal>
//   <h2 slot="header">Подтверждение</h2>
//   <p>Вы уверены?</p>
//   <div slot="footer">
//     <button>Отмена</button>
//     <button>Подтвердить</button>
//   </div>
// </my-modal>

Множественные элементы в slot

class TabPanel extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        .tabs {
          display: flex;
          border-bottom: 2px solid #ddd;
          margin-bottom: 10px;
        }
        .tab-buttons {
          display: flex;
          gap: 10px;
        }
        button {
          background: none;
          border: none;
          padding: 10px 15px;
          cursor: pointer;
          border-bottom: 3px solid transparent;
        }
        button.active {
          border-bottom: 3px solid #007bff;
          color: #007bff;
        }
        .content {
          display: none;
        }
        .content.active {
          display: block;
        }
      </style>
      <div class="tabs">
        <div class="tab-buttons">
          <slot name="tab-button"></slot>
        </div>
      </div>
      <div class="tab-content">
        <slot name="tab-content"></slot>
      </div>
    `;
  }
}

customElements.define('my-tabs', TabPanel);

// Использование:
// <my-tabs>
//   <button slot="tab-button">Tab 1</button>
//   <button slot="tab-button">Tab 2</button>
//   <button slot="tab-button">Tab 3</button>
//   
//   <div slot="tab-content">Content 1</div>
//   <div slot="tab-content">Content 2</div>
//   <div slot="tab-content">Content 3</div>
// </my-tabs>

Работа со Slot API

class TabPanel extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        .content { display: none; }
        .content.active { display: block; }
      </style>
      <div class="tabs-header">
        <slot name="header"></slot>
      </div>
      <div class="tabs-content">
        <slot name="content"></slot>
      </div>
    `;
    
    // Получить все элементы в named slot
    const contentSlot = this.shadowRoot.querySelector('slot[name="content"]');
    const assignedElements = contentSlot.assignedElements();
    
    console.log('Количество tabs:', assignedElements.length);
    assignedElements.forEach((el, i) => {
      el.classList.add(`tab-${i}`);
    });
    
    // Слушать изменения в slot
    contentSlot.addEventListener('slotchange', (event) => {
      console.log('Содержимое slot изменилось');
      const nodes = contentSlot.assignedNodes();
      console.log('Элементы:', nodes);
    });
  }
}

customElements.define('my-tab-panel', TabPanel);

Слотирование с фильтрацией

class List extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        ul { list-style: none; padding: 0; }
        li { padding: 8px; border-bottom: 1px solid #eee; }
      </style>
      <ul>
        <slot></slot>
      </ul>
    `;
    
    // Получить все li элементы через default slot
    const slot = this.shadowRoot.querySelector('slot');
    
    slot.addEventListener('slotchange', () => {
      const items = slot.assignedElements()
        .filter(el => el.tagName === 'LI');
      
      console.log(`Всего items: ${items.length}`);
      
      // Стилизовать через светлый DOM
      items.forEach((item, i) => {
        item.style.backgroundColor = i % 2 === 0 ? '#f5f5f5' : 'white';
      });
    });
  }
}

customElements.define('my-list', List);

// Использование:
// <my-list>
//   <li>Item 1</li>
//   <li>Item 2</li>
//   <li>Item 3</li>
// </my-list>

Fallback содержимое

class Tooltip extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    const text = this.getAttribute('text') || 'Default tooltip';
    
    this.shadowRoot.innerHTML = `
      <style>
        .tooltip {
          position: relative;
          display: inline-block;
          cursor: help;
        }
        .tooltip-text {
          visibility: hidden;
          background: #333;
          color: white;
          padding: 5px 10px;
          border-radius: 4px;
          position: absolute;
          z-index: 1;
          bottom: 125%;
          left: 50%;
          margin-left: -50px;
          opacity: 0;
          transition: opacity 0.3s;
        }
        .tooltip:hover .tooltip-text {
          visibility: visible;
          opacity: 1;
        }
      </style>
      <div class="tooltip">
        <slot>Hover me</slot>
        <span class="tooltip-text">${text}</span>
      </div>
    `;
  }
}

customElements.define('my-tooltip', Tooltip);

// Использование:
// <my-tooltip text="Это подсказка">?
// <my-tooltip text="Default"> (без содержимого, будет использовано fallback)

Выводы

  1. Default slot - <slot></slot> принимает всё содержимое
  2. Named slots - <slot name="header"></slot> для конкретных областей
  3. Slot API - assignedElements(), assignedNodes(), slotchange событие
  4. Fallback - содержимое внутри <slot> показывается если нет слотированного контента
  5. Стилизация - Shadow DOM стили не влияют на слотированный контент
  6. Best practice - Используй named slots для структурированных компонентов