Вопрос или проблема
Я создаю пользовательский веб-компонент, используя чистый 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, когда элемент будет подключен.
Вот как вы можете реализовать это:
- Используйте механизм "проверки" для отслеживания изменений (например, атрибутов или дочерних элементов), чтобы возможно было выполнять рендеринг в нужный момент.
- Соблюдайте порядок операций — сначала создавайте и готовьте 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
, а также обновлять свое содержимое, если изменятся атрибуты после рендеринга.