DevGang
Авторизоваться

Анатомия веб-компонента: Основы

Я изучаю веб-компоненты в рамках курса Роба Айзенберга "Разработка веб-компонентов" и решил, что мне стоит изложить свои знания в письменном виде. Итак, здесь представлен очень простой веб-компонент, демонстрирующий некоторые фундаментальные характеристики веб-компонентов, которые мы будем развивать в будущем (я прошел всего несколько уроков, а впереди еще очень много).

Здесь мы создадим элемент my-counter, который на самом деле очень прост. Все, что он делает, - это выводит в DOM значение, содержащееся в свойстве count. Так, что это:

<my-counter count="3"></my-counter>

будет выглядеть следующим образом:

3

Если не указать count, то будет выведен 0.

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

Сначала HTML:

<template id="my-counter">
    <div id="count"></div>
</template>

<my-counter count="0"></my-counter>

А теперь JavaScript:

class MyCounter extends HTMLElement {
  static observedAttributes = ["count"];
  static #fragment = null;

  #view = null;
  #count;

  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    if (this.#view === null) {
      this.#view = this.#createView();
      this.shadowRoot.appendChild(this.#view);
      this.#count = this.shadowRoot.getElementById("count");
    }

    this.countChanged();
  }

  attributeChangedCallback() {
    this.countChanged();
  }

  countChanged() {
    if (this.#count) {
      const value = this.getAttribute("count") ?? 0;
      this.#count.innerText = value;
    }
  }

  #createView() {
    if (MyCounter.#fragment === null) {
      const template = document.getElementById("my-counter");
      MyCounter.#fragment = document.adoptNode(template.content);
    }

    return MyCounter.#fragment.cloneNode(true);
  }
}

customElements.define("my-counter", MyCounter);

Объявление класса MyCounter

class MyCounter extends HTMLElement {

MyCounter расширяет базовый HTMLElement, превращая его в новый тип HTML-элемента. Теперь мы можем использовать этот класс MyCounter для добавления собственных поведений, сохраняя при этом все свойства и методы обычного HTMLElement.

Наблюдаемые атрибуты

  static observedAttributes = ["count"];

Свойство static observedAttributes определяет список атрибутов, которые компонент будет отслеживать. Когда атрибут count изменяется, MyCounter реагирует на это обновлением отображаемого значения компонента.

Эффективная работа с шаблонами

  static #fragment = null;

Объявив свойство static #fragment и сохранив в нем фрагмент шаблона при создании первого экземпляра HTMLElement MyCounter, MyCounter избегает избыточного принятия шаблонов для нескольких экземпляров. Этот общий фрагмент служит образцом для представления каждого экземпляра.

DOM для конкретного экземпляра

  #view = null;

После того как шаблон принят, мы можем клонировать его для каждого экземпляра счетчика с помощью clone. Свойство #view содержит клонированный шаблон для экземпляра компонента. В отличие от общего фрагмента, это свойство является специфичным для каждого экземпляра, создавая уникальный вид для каждого счетчика на странице.

Ссылки на частные элементы

  #count;

MyCounter использует приватное поле #count для хранения ссылки на конкретный элемент DOM, отображающий счетчик. Эта ссылка устанавливается в connectedCallback, что обеспечивает независимое обновление отображения каждого экземпляра компонента.

Обратные вызовы жизненного цикла

Соединение компонентов

  connectedCallback() {
    // If the view has not yet been initialised, we need to
    // create it by adopting the template into the DOM, then cloning.
    if (this.#view === null) {
      this.#view = this.#createView();
      this.shadowRoot.appendChild(this.#view);
      this.#count = this.shadowRoot.getElementById("count");
    }

    this.countChanged();
  }

Когда экземпляр добавляется в DOM, срабатывает connectedCallback. Этот обратный вызов отвечает за инициализацию представления экземпляра, если она еще не была выполнена, добавление его в теневой DOM и кэширование элемента count.

Здесь также необходимо вызвать метод countChanged, поскольку обратный вызов attributeChangedCallback может еще не сработать (т.е. свойство count может не иметь значения), и поэтому нам необходимо отобразить значение по умолчанию, равное 0.

Реагирование на изменение атрибутов

  attributeChangedCallback() {
    this.countChanged();
  }

  countChanged() {
    // If the id is present it means the element has been connected to the DOM.
    if (this.#count) {
      const value = this.getAttribute("count") ?? 0;
      this.#count.innerText = value;
    }
  }

Методы attributeChangedCallback и countChanged работают параллельно, обновляя отображение компонента при изменении атрибута count. Это обеспечивает постоянную синхронизацию пользовательского интерфейса с состоянием компонента.

Усвоение и клонирование шаблонов

  #createView() {
    if (MyCounter.#fragment === null) {
      const template = document.getElementById("my-counter");
      MyCounter.#fragment = document.adoptNode(template.content);
    }

    return MyCounter.#fragment.cloneNode(true);
  }

Метод #createView проверяет, был ли шаблон принят классом. Если нет, то он принимает шаблон в документ. Затем он клонирует этот шаблон, чтобы создать представление для экземпляра. Этот процесс является важной оптимизацией производительности, позволяющей избежать лишних операций над DOM.

Регистрация компонентов

customElements.define("my-counter", MyCounter);

Наконец, MyCounter регистрируется как пользовательский элемент в файле customElements.define, что позволяет разработчикам использовать теги <my-counter> непосредственно в HTML, как и любой другой стандартный элемент.

Заключение

MyCounter демонстрирует самые базовые характеристики создания и обновления пользовательского элемента на низком уровне.

Надеюсь, вам понравилось читать эту статью!

Источник:

#JavaScript #HTML
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу