Custom Elements API

Custom Elements API является фундаментом для создания переиспользуемых компонентов в Polymer. Он позволяет определять собственные HTML-теги с уникальным поведением, структурой и стилями, интегрированными в DOM.

Создание пользовательского элемента

Для создания нового элемента используется метод customElements.define(tagName, class). Класс должен наследоваться от HTMLElement или других встроенных элементов через extends.

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `<p>Привет, Polymer!</p>`;
  }
}

customElements.define('my-element', MyElement);

Ключевые моменты:

  • tagName должен содержать дефис (-), иначе элемент не будет признан пользовательским.
  • attachShadow({mode: 'open'}) создаёт Shadow DOM, обеспечивая инкапсуляцию стилей и структуры.
  • Конструктор вызывается при создании элемента в DOM, что позволяет сразу инициализировать внутреннее состояние.

Свойства и атрибуты

Polymer расширяет стандарт Custom Elements, обеспечивая удобную работу с реактивными свойствами через properties. Свойства могут быть связаны с атрибутами, что позволяет синхронизировать состояние элемента с DOM.

class MyElement extends HTMLElement {
  static get observedAttributes() {
    return ['name'];
  }

  get name() {
    return this._name;
  }

  set name(value) {
    this._name = value;
    this.render();
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    if (attrName === 'name') {
      this.name = newVal;
    }
  }

  render() {
    this.shadowRoot.innerHTML = `<p>Привет, ${this.name}!</p>`;
  }
}

customElements.define('my-element', MyElement);

Особенности:

  • observedAttributes указывает, за какими атрибутами следует следить.
  • attributeChangedCallback вызывается при изменении атрибута в DOM.
  • Свойства и атрибуты могут быть связаны через геттеры и сеттеры, обеспечивая реактивное обновление интерфейса.

Жизненный цикл Custom Elements

Custom Elements обладают рядом методов жизненного цикла:

  • constructor() – инициализация элемента.
  • connectedCallback() – вызывается при добавлении элемента в DOM.
  • disconnectedCallback() – вызывается при удалении элемента из DOM.
  • adoptedCallback() – вызывается при переносе элемента в другой документ.
  • attributeChangedCallback(name, oldValue, newValue) – реагирует на изменения атрибутов.

Пример использования:

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.render();
  }

  disconnectedCallback() {
    console.log('Элемент удалён из DOM');
  }

  render() {
    this.shadowRoot.innerHTML = `<p>Элемент подключён к DOM</p>`;
  }
}

customElements.define('my-element', MyElement);

Shadow DOM и инкапсуляция

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

  • attachShadow({mode: 'open'}) создаёт открытый Shadow DOM, доступный через shadowRoot.
  • attachShadow({mode: 'closed'}) создаёт закрытый Shadow DOM, недоступный извне.

Пример использования стилей в Shadow DOM:

this.shadowRoot.innerHTML = `
  <style>
    p {
      color: blue;
      font-weight: bold;
    }
  </style>
  <p>Текст в изолированном стиле</p>
`;

Наследование встроенных элементов

Custom Elements могут наследоваться от встроенных HTML-тегов, что позволяет расширять их функциональность:

class MyButton extends HTMLButtonElement {
  constructor() {
    super();
    this.addEventListener('click', () => alert('Нажато!'));
  }
}

customElements.define('my-button', MyButton, { extends: 'button' });

Использование:

<button is="my-button">Кнопка</button>

Интеграция с Polymer

Polymer предоставляет удобный синтаксис для декларативного описания компонентов и их реактивного поведения. Компоненты Polymer используют Custom Elements API как основу и добавляют:

  • properties для реактивного состояния.
  • template для декларативного рендеринга.
  • dom-bind и dom-repeat для привязки данных и повторяющихся элементов.

Пример простого Polymer-компонента:

import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';

class MyPolymerElement extends PolymerElement {
  static get properties() {
    return {
      name: { type: String, value: 'Polymer' }
    };
  }

  static get template() {
    return html`
      <p>Привет, [[name]]!</p>
    `;
  }
}

customElements.define('my-polymer-element', MyPolymerElement);

Ключевые моменты:

  • Синтаксис [[property]] обеспечивает реактивное связывание данных.
  • Свойства автоматически обновляют DOM при изменении.
  • Polymer интегрирует Shadow DOM и Custom Elements для построения модульных и изолированных компонентов.

События и взаимодействие

Custom Elements позволяют генерировать и обрабатывать собственные события. Polymer расширяет это через notify и декларативные слушатели.

this.dispatchEvent(new CustomEvent('name-changed', {
  detail: { value: this.name },
  bubbles: true,
  composed: true
}));
  • bubbles: true позволяет событию подниматься вверх по DOM.
  • composed: true позволяет событию проходить через Shadow DOM.

Custom Elements API является основой компонентного подхода в Polymer, обеспечивая мощную инкапсуляцию, реактивное обновление и расширяемость встроенных HTML-элементов. В сочетании с декларативной привязкой данных и шаблонами Polymer создаются гибкие и переиспользуемые веб-компоненты.