Lit построен поверх веб-стандарта Custom Elements. Это означает, что любой компонент Lit — это прежде всего пользовательский HTML-элемент, определённый через спецификацию Web Components. Понимание Custom Elements критично для правильного проектирования компонентов, их жизненного цикла, взаимодействия с DOM и интеграции с другими фреймворками.
Custom Elements позволяют создавать собственные HTML-теги с инкапсулированной логикой, состоянием и поведением, которые работают нативно в браузере без транспиляции или сторонних рантаймов.
Регистрация происходит через глобальный реестр
customElements. Каждый элемент должен иметь уникальное имя,
содержащее дефис.
customElements.define('my-element', MyElement);
Класс элемента обязан наследоваться от HTMLElement или
одного из его потомков. В Lit используется LitElement,
который сам расширяет HTMLElement и добавляет реактивность,
шаблоны и управление обновлениями.
import { LitElement, html } from 'lit';
class MyElement extends LitElement {
render() {
return html`<p>Пример</p>`;
}
}
customElements.define('my-element', MyElement);
Повторная регистрация с тем же именем приводит к исключению. Поэтому в библиотеках часто применяется проверка:
if (!customElements.get('my-element')) {
customElements.define('my-element', MyElement);
}
Спецификация определяет строгий жизненный цикл, который Lit использует и расширяет.
Вызывается при создании экземпляра элемента. В этот момент:
constructor() {
super();
this.value = 0;
}
В Lit в constructor инициализируется внутреннее
состояние, но не выполняется рендеринг.
Вызывается каждый раз, когда элемент добавляется в DOM.
connectedCallback() {
super.connectedCallback();
window.addEventListener('resize', this.onResize);
}
Lit использует этот хук для запуска реактивного обновления и первого рендера.
Вызывается при удалении элемента из DOM.
disconnectedCallback() {
window.removeEventListener('resize', this.onResize);
super.disconnectedCallback();
}
Вызывается при изменении наблюдаемых атрибутов.
static get observedAttributes() {
return ['title'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this[name] = newValue;
}
}
Lit автоматически синхронизирует атрибуты и свойства через систему
@property, поэтому ручная реализация почти никогда не
требуется.
Custom Elements различают атрибуты (строки в HTML) и свойства (значения JavaScript). Lit предоставляет декларативный механизм связывания.
static properties = {
count: { type: Number, reflect: true }
};
type управляет преобразованиемreflect: true отражает значение свойства обратно в
HTML-атрибутПример использования:
<my-counter count="5"></my-counter>
this.count += 1;
Lit автоматически выполнит:
Custom Elements по спецификации не имеют встроенного механизма обновлений. Lit добавляет собственный реактивный слой поверх жизненного цикла.
Каждое изменение реактивного свойства помечает компонент как «грязный» и планирует обновление микротаской.
Ключевые методы:
requestUpdate()shouldUpdate(changedProperties)update(changedProperties)updated(changedProperties)firstUpdated(changedProperties)shouldUpdate(changed) {
return changed.has('count');
}
Эта логика не является частью Custom Elements, но полностью зависит от корректной работы их жизненного цикла.
Спецификация поддерживает наследование от стандартных тегов
(HTMLButtonElement, HTMLInputElement), но
браузерная поддержка и синтаксис сложны.
class FancyButton extends HTMLButtonElement {}
customElements.define('fancy-button', FancyButton, {
extends: 'button'
});
Использование:
<button is="fancy-button"></button>
Lit не рекомендует этот подход из-за ограниченной поддержки и сложности стилизации. Предпочтение отдаётся автономным элементам.
Хотя Shadow DOM формально является отдельной спецификацией, Custom Elements практически всегда используются вместе с ним.
Lit по умолчанию создаёт shadow root:
createRenderRoot() {
return this.attachShadow({ mode: 'open' });
}
Преимущества:
Shadow DOM не обязателен. Его можно отключить:
createRenderRoot() {
return this;
}
Это превращает компонент в обычный DOM-узел без изоляции.
Custom Elements используют стандартную систему событий DOM. Lit не вводит собственный механизм, а лишь упрощает работу.
Создание события:
this.dispatchEvent(new CustomEvent('change', {
detail: { value: this.value },
bubbles: true,
composed: true
}));
bubbles — прохождение вверх по DOMcomposed — выход за пределы Shadow DOMПодписка:
<my-element @change=${this.onChange}></my-element>
Это полностью соответствует стандартам Custom Elements и не требует адаптеров.
Custom Elements могут использоваться в DOM до регистрации. Браузер
автоматически «апгрейдит» элементы после define.
<my-element></my-element>
<script type="module">
import './my-element.js';
</script>
Lit корректно обрабатывает такой сценарий:
Для ожидания регистрации используется:
customElements.whenDefined('my-element').then(() => {
// элемент гарантированно доступен
});
Custom Elements накладывают ряд строгих ограничений:
Lit учитывает эти ограничения и строит архитектуру без нарушения спецификации.
Компоненты Lit, как Custom Elements:
Это достигается именно за счёт строгого следования спецификации Custom Elements без абстракций поверх неё.
Lit не заменяет Custom Elements, а реализует поверх них декларативный, типобезопасный и производительный слой. Все ключевые механизмы Lit — свойства, обновления, шаблоны, стили — существуют только благодаря возможностям, предоставляемым спецификацией Custom Elements.