Как отобразить мой веб-компонент до connectedCallback, но после конструктора?

Вопрос или проблема

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

Сначала я вызывал render() и hydrate() внутри конструктора компонента. Это хорошо работало, когда компонент уже был частью DOM, однако, если компонент был создан с помощью document.createElement("my-button"), вызов render() выполнялся до того, как я успевал добавить атрибуты и дочерние элементы и т.д., что было серьезной проблемой.

Другой вариант, который показан в моем коде ниже, заключается в том, чтобы вызывать render() и hydrate() внутри метода connectedCallback(). Это решает проблему с document.createElement("my-button"), однако приводит к новой проблеме. Поскольку connectedCallback() выполняется после добавления элемента в DOM, перед завершением отрисовки компонента может произойти FOUC (вспышка неоформленного контента). Было бы неплохо, если бы существовал beforeConnectedCallback(), чтобы я мог выполнить код до добавления элемента в DOM, но, похоже, этого не существует.

Что мне делать, чтобы мой компонент автоматически отрисовывался и гидратировался перед добавлением в DOM?

Вот мой компонент:

class MYButton extends HTMLElement {

    constructor() {
        super();
    }

    connectedCallback() {
        this.render();
        this.hydrate();
    }

    render() {
        let type = this.getAttribute("type");
        if (type === "link") {
            this.elmButton = document.createElement("a");
            this.elmButton.setAttribute("href", this.getAttribute("href"));
        } else {
            this.elmButton = document.createElement("button");
            this.elmButton.setAttribute("type", type);
        }
        while (this.firstChild) {
            this.elmButton.appendChild(this.firstChild);
        }
        this.appendChild(this.elmButton);
    }

    hydrate() {
        this.elmButton.addEventListener("click", () => alert("hello world"));
    }
}

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

Ответ или решение

Для решения проблемы с рендерингом вашего веб-компонента до connectedCallback, но после вызова конструктора, вы можете использовать подход с асинхронным рендерингом, а также отслеживанием изменения атрибутов и содержимого элемента. Ваши опасения по поводу Flash of Unstyled Content (FOUC) очень обоснованны, и это можно решить, создав контент заранее, а затем добавив его в DOM, когда элемент будет подключен.

Вот как вы можете реализовать это:

  1. Используйте механизм "проверки" для отслеживания изменений (например, атрибутов или дочерних элементов), чтобы возможно было выполнять рендеринг в нужный момент.
  2. Соблюдайте порядок операций — сначала создавайте и готовьте HTML, а затем добавляйте его в методе connectedCallback.

Ниже приведён исправленный код вашего компонента, который учитывает указанные моменты:

class MYButton extends HTMLElement {
    constructor() {
        super();
        this.rendered = false; // Флаг, изменяющийся при рендеринге
    }

    connectedCallback() {
        // Рендерим только в случае, если еще не отрендерен
        if (!this.rendered) {
            this.render();
            this.hydrate();
            this.rendered = true; // Устанавливаем флаг
        }
    }

    // Метод render, который будет вызываться для рендеринга элемента
    render() {
        // Создаём кнопку на основе атрибута
        let type = this.getAttribute("type");
        if (type === "link") {
            this.elmButton = document.createElement("a");
            this.elmButton.setAttribute("href", this.getAttribute("href"));
        } else {
            this.elmButton = document.createElement("button");
            this.elmButton.setAttribute("type", type);
        }

        // Переносим дочерние элементы в новую кнопку
        while (this.firstChild) {
            this.elmButton.appendChild(this.firstChild);
        }

        // Добавляем кнопку в компонент
        this.appendChild(this.elmButton);
    }

    // Метод hydrate для добавления обработчиков событий и прочего
    hydrate() {
        this.elmButton.addEventListener("click", () => alert("hello world"));
    }

    // Метод, который будет срабатывать при изменении атрибутов
    static get observedAttributes() {
        return ['type', 'href']; // Указываем, за какими атрибутами следим
    }

    attributeChangedCallback(name, oldValue, newValue) {
        // Если атрибут изменился, делаем повторный рендеринг
        if (this.rendered) {
            this.render(); // Повторный рендеринг при изменении атрибутов
        }
    }
}
customElements.define('my-button', MYButton);

Примечания к коду:

  • Мы добавили флаг rendered, который позволяет избежать повторного рендеринга, если элемент уже отрендерен.
  • Использование статического метода observedAttributes позволяет следить за изменениями атрибутов, чтобы обновить компонент при необходимости.
  • Метод attributeChangedCallback автоматически вызовется, когда изменяется один из наблюдаемых атрибутов, что даёт возможность обновить контент.

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

Оцените материал
Добавить комментарий

Капча загружается...