Lit — современный JavaScript-фреймворк для создания веб-компонентов, предоставляющий удобный синтаксис для работы с шаблонами и реактивными данными. Одной из ключевых возможностей является эффективная обработка событий и использование делегирования для повышения производительности и упрощения структуры компонентов.
События в Lit привязываются напрямую в шаблоне через директиву
@event. Синтаксис прост и лаконичен:
import { LitElement, html } from 'lit';
class MyButton extends LitElement {
handleClick(e) {
console.log('Кнопка нажата', e);
}
render() {
return html`<button @click=${this.handleClick}>Нажми меня</button>`;
}
}
customElements.define('my-button', MyButton);
Особенности привязки:
@click=${this.handleClick} — добавляет слушатель
события click.@click=${(e) => this.handleClick(e, 'дополнительный аргумент')}
Делегирование событий позволяет минимизировать количество слушателей, прикрепленных к элементам, что критично для динамически создаваемых списков и больших DOM-структур.
Пример делегирования на родительском элементе:
import { LitElement, html } from 'lit';
class ItemList extends LitElement {
handleClick(e) {
const item = e.target.closest('.item');
if (item) {
console.log('Нажат элемент:', item.dataset.id);
}
}
render() {
return html`
<div @click=${this.handleClick}>
<div class="item" data-id="1">Элемент 1</div>
<div class="item" data-id="2">Элемент 2</div>
<div class="item" data-id="3">Элемент 3</div>
</div>
`;
}
}
customElements.define('item-list', ItemList);
Ключевые моменты делегирования:
e.target.closest('.item') позволяет
определить, на какой элемент произошло событие.Lit поддерживает стандартные DOM-события, а также пользовательские
события (CustomEvent). Для генерации и распространения событий
используется dispatchEvent:
this.dispatchEvent(new CustomEvent('item-selected', {
detail: { id: 42 },
bubbles: true,
composed: true
}));
Параметры:
detail — объект с данными, передаваемыми
слушателю.bubbles — позволяет событию подниматься вверх по
DOM.composed — разрешает событию проходить через Shadow
DOM.Слушатели пользовательских событий также можно привязать через
@:
<item-list @item-selected=${this.onItemSelected}></item-list>
once для одноразового
события:<button @click=${(e) => this.handleClick(e)} .once=${true}>Нажми один раз</button>
В Lit обработчики могут быть асинхронными, что удобно для работы с API или задержками:
async handleClick(e) {
const data = await fetch('/api/data').then(r => r.json());
this.data = data;
}
Асинхронные обработчики не блокируют рендер и позволяют безопасно изменять состояние компонента после завершения операции.
Внутри Shadow DOM обычные события могут не проходить наружу без
composed: true. Lit автоматически обрабатывает биндинг
событий в Shadow DOM, но при необходимости поднять событие наружу нужно
использовать CustomEvent с composed: true.
this.dispatchEvent(new CustomEvent('close-modal', { bubbles: true, composed: true }));
Это важно для взаимодействия компонентов в сложных приложениях.
Комбинируя делегирование с реактивными свойствами, можно создавать динамические интерактивные интерфейсы без избыточных слушателей:
import { LitElement, html } from 'lit';
class TodoList extends LitElement {
static properties = {
todos: { type: Array }
};
constructor() {
super();
this.todos = [
{ id: 1, text: 'Купить молоко', done: false },
{ id: 2, text: 'Написать статью', done: false }
];
}
toggleDone(e) {
const item = e.target.closest('.todo-item');
if (!item) return;
const id = Number(item.dataset.id);
this.todos = this.todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
);
}
render() {
return html`
<ul @click=${this.toggleDone}>
${this.todos.map(todo => html`
<li class="todo-item" data-id=${todo.id} style="text-decoration: ${todo.done ? 'line-through' : 'none'}">
${todo.text}
</li>
`)}
</ul>
`;
}
}
customElements.define('todo-list', TodoList);
Данный подход позволяет:
Глубокое понимание привязки событий, делегирования и взаимодействия с реактивными свойствами является основой эффективного использования Lit в разработке сложных веб-приложений.