События
События — стандартный способ, с помощью которого элементы сообщают об изменениях. Обычно эти изменения происходят в результате взаимодействия с пользователем. Например, кнопка отправляет событие щелчка, когда пользователь нажимает на неё; поле ввода отправляет событие изменения, когда пользователь вводит в него значение.
Помимо этих стандартных событий, которые отправляются автоматически, элементы Lit могут отправлять пользовательские события. Например, элемент меню может отправлять событие, чтобы сообщить об изменении выбранного пункта; всплывающий элемент может отправлять событие при открытии или закрытии всплывающего окна.
Любой код JavaScript, в том числе сами элементы Lit, может прослушивать события и выполнять действия в ответ на них. Например, элемент панели инструментов может отфильтровать список при выборе пункта меню; элемент входа в систему может обработать вход, когда он получает щелчок по кнопке входа.
Прослушивание событий
Помимо стандартного API addEventListener, Lit представляет декларативный способ добавления обработчиков событий.
Добавление обработчиков событий в шаблон элемента
В шаблоне можно использовать выражения @, чтобы добавлять обработчики событий для элементов в шаблоне компонента. Декларативные обработчики событий добавляются при отображении шаблона.
Настройка параметров обработчиков событий
Если нужно настроить параметры события для декларативного обработчика (например, passive или capture), их можно указать для обработчика с помощью декоратора @eventOptions. Объект, переданный в @eventOptions, передаётся как параметр options в addEventListener.
import {LitElement, html} from 'lit';
import {eventOptions} from 'lit/decorators.js';
//...
@eventOptions({passive: true})
private _handleTouchStart(e) { console.log(e.type) }
Использование декораторов. Декораторы — предлагаемая возможность JavaScript, поэтому для их использования потребуется компилятор, например Babel или TypeScript. Подробности см. в разделе Включение декораторов.
Если вы не используете декораторы, можно настроить параметры обработчика события, передав объект в выражение обработчика. Объект должен иметь метод handleEvent() и может содержать любые параметры, которые обычно указываются в аргументе options метода addEventListener().
render() {
return html`<button @click=${{handleEvent: () => this.onClick(), once: true}}>click</button>`
}
Добавление обработчиков событий для компонента или его теневого корня
Чтобы получать уведомления о событиях, отправленных дочерними элементами компонента, размещёнными в слотах, а также дочерними элементами, отображёнными в shadow DOM с помощью шаблона компонента, можно добавить обработчик непосредственно компоненту с помощью стандартного метода DOM addEventListener. Полное описание см. в разделе EventTarget.addEventListener() на MDN.
Конструктор компонента — подходящее место для добавления обработчиков событий компонента.
constructor() {
super();
this.addEventListener('click', (e) => console.log(e.type, e.target.localName));
}
Добавление обработчиков событий непосредственно компоненту — это разновидность делегирования событий, которая позволяет сократить код или повысить производительность. Подробности см. в разделе делегирование событий. Обычно в этом случае свойство события target используется для выполнения действий с учётом того, какой элемент вызвал событие.
Однако при получении обработчиком компонента события, отправленного из его shadow DOM, цель события переназначается. Это означает, что целью события становится сам компонент. Дополнительную информацию см. в разделе Работа с событиями в shadow DOM.
Переназначение цели может мешать делегированию событий. Чтобы избежать этого, обработчики можно добавлять непосредственно корню shadow DOM компонента. Поскольку shadowRoot недоступен в constructor, обработчики событий можно добавлять в методе createRenderRoot следующим образом. Обратите внимание: важно вернуть корень shadow DOM из метода createRenderRoot.
Добавление обработчиков событий для других элементов
Если компонент добавляет обработчик события для чего-либо, кроме самого себя или элементов DOM, созданных с помощью его шаблона — например, для Window, Document или элемента в основном DOM, — добавьте обработчик в connectedCallback и удалите его в disconnectedCallback.
Удаление обработчика события в
disconnectedCallbackгарантирует, что вся память, выделенная компонентом, будет освобождена при уничтожении компонента или его отключении от страницы.Добавление обработчика события в
connectedCallback(вместо, например, конструктора илиfirstUpdated) гарантирует, что компонент повторно создаст обработчик события, если его отключат от DOM, а затем снова подключат.
connectedCallback() {
super.connectedCallback();
window.addEventListener('resize', this._handleResize);
}
disconnectedCallback() {
window.removeEventListener('resize', this._handleResize);
super.disconnectedCallback();
}
Дополнительную информацию о connectedCallback и disconnectedCallback см. в документации MDN об использовании пользовательских элементов и их обратных вызовах жизненного цикла.
Оптимизация производительности
Добавление обработчиков событий выполняется очень быстро и обычно не влияет на производительность. Однако для компонентов, которые часто используются и которым требуется много обработчиков событий, можно ускорить первоначальное отображение, сократив количество обработчиков с помощью делегирования событий и добавляя обработчики асинхронно после отображения.
Делегирование событий
Делегирование событий позволяет сократить количество обработчиков и тем самым повысить производительность. Иногда также удобно централизовать обработку событий, чтобы сократить код. Делегирование событий можно использовать только для обработки событий, которые bubble. Подробности о всплытии событий см. в разделе Отправка событий.
Всплывающие события можно перехватывать на любом родительском элементе в DOM. Этим можно воспользоваться, добавив один обработчик события родительскому компоненту, чтобы получать уведомления о всплывающих событиях, отправленных любыми его потомками в DOM. Используйте свойство события target, чтобы выполнять действия с учётом элемента, отправившего событие.
Асинхронное добавление обработчиков событий
Чтобы добавить обработчик события после отображения, используйте метод firstUpdated. Это обратный вызов жизненного цикла Lit, который выполняется после первого обновления компонента и отображения созданного шаблоном DOM.
Обратный вызов firstUpdated выполняется после того, как компонент впервые обновился и вызвал метод render, но до того, как браузер успел выполнить отрисовку.
Дополнительную информацию см. в разделе firstUpdated документации по жизненному циклу.
Чтобы обработчик был добавлен после того, как пользователь сможет увидеть компонент, можно дождаться Promise, который разрешается после отрисовки браузером.
async firstUpdated() {
// Give the browser a chance to paint
await new Promise((r) => setTimeout(r, 0));
this.addEventListener('click', this._handleClick);
}
Понимание значения this в обработчиках событий
Обработчики событий, добавленные с помощью декларативного синтаксиса @ в шаблоне, автоматически привязываются к компоненту.
Поэтому в любом декларативном обработчике событий можно использовать this для ссылки на экземпляр компонента:
class MyElement extends LitElement {
render() {
return html`<button @click="${this._handleClick}">click</button>`;
}
_handleClick(e) {
console.log(this.prop);
}
}
При императивном добавлении обработчиков с помощью addEventListener используйте стрелочную функцию, чтобы this ссылалось на компонент:
export class MyElement extends LitElement {
private _handleResize = () => {
// `this` refers to the component
console.log(this.isConnected);
}
constructor() {
window.addEventListener('resize', this._handleResize);
}
}
Дополнительную информацию см. в документации MDN по this.
Прослушивание событий, отправленных повторяющимися шаблонами
При прослушивании событий повторяющихся элементов часто удобно использовать делегирование событий, если событие всплывает. Если событие не всплывает, обработчик можно добавить к повторяющимся элементам. Ниже приведён пример обоих способов:
Удаление обработчиков событий
Передача null, undefined или nothing в выражение @ приведёт к удалению существующего обработчика.
Отправка событий
Все узлы DOM могут отправлять события с помощью метода dispatchEvent. Сначала создайте экземпляр события, указав его тип и параметры. Затем передайте его в dispatchEvent, как показано ниже:
const event = new Event('my-event', {bubbles: true, composed: true});
myElement.dispatchEvent(event);
Параметр bubbles позволяет событию распространяться вверх по дереву DOM к родительским элементам отправившего его элемента. Если вы хотите, чтобы событие могло участвовать в делегировании событий, обязательно установите этот флаг.
Параметр composed полезно установить, чтобы событие могло распространяться за пределы дерева shadow DOM, в котором находится элемент.
Дополнительную информацию см. в разделе Работа с событиями в shadow DOM.
Полное описание отправки событий см. в разделе EventTarget.dispatchEvent() на MDN.
Когда отправлять событие
События следует отправлять в ответ на взаимодействие с пользователем или асинхронные изменения состояния компонента. Как правило, их не следует отправлять в ответ на изменение состояния, внесённое владельцем компонента с помощью API его свойств или атрибутов. Именно так обычно работают встроенные элементы веб-платформы.
Например, когда пользователь вводит значение в элемент input, отправляется событие change, но если код задаёт свойство value элемента input, событие change не отправляется.
Аналогично, компонент меню должен отправлять событие, когда пользователь выбирает пункт меню, но не должен отправлять событие, если, например, задано свойство selectedItem меню.
Обычно это означает, что компонент должен отправлять событие в ответ на другое событие, которое он прослушивает.
Отправка событий после обновления элемента
Часто событие следует отправлять только после обновления и отображения элемента. Это может быть необходимо, если событие предназначено для сообщения об изменении отображаемого состояния в результате взаимодействия с пользователем. В этом случае после изменения состояния, но до отправки события, можно дождаться Promise updateComplete компонента.
Использование стандартных или пользовательских событий
События можно отправлять, создавая экземпляр Event или CustomEvent. Оба подхода вполне подходят. При использовании CustomEvent данные события передаются в свойстве detail события. При использовании Event можно создать подкласс события и добавить к нему собственный API.
Подробности о создании событий см. в разделе Event на MDN.
Отправка пользовательского события:
const event = new CustomEvent('my-event', {
detail: {
message: 'Something important happened'
}
});
this.dispatchEvent(event);
Дополнительную информацию см. в документации MDN о пользовательских событиях.
Отправка стандартного события:
class MyEvent extends Event {
constructor(message) {
super();
this.type = 'my-event';
this.message = message;
}
}
const event = new MyEvent('Something important happened');
this.dispatchEvent(event);
Работа с событиями в shadow DOM
При использовании shadow DOM стандартная система событий имеет несколько особенностей, которые важно понимать. Shadow DOM предназначен прежде всего для создания области видимости в DOM, которая инкапсулирует сведения об этих «теневых» элементах. Поэтому события в shadow DOM скрывают некоторые сведения от внешних элементов DOM.
Понимание отправки составных событий
По умолчанию событие, отправленное внутри корня shadow DOM, не будет видно за его пределами. Чтобы событие проходило через границы shadow DOM, необходимо установить свойство composed в значение true. Обычно composed используется вместе с bubbles, чтобы событие могли видеть все узлы дерева DOM:
_dispatchMyEvent() {
let myEvent = new CustomEvent('my-event', {
detail: { message: 'my-event happened.' },
bubbles: true,
composed: true });
this.dispatchEvent(myEvent);
}
Если событие является composed и bubble, его могут получить все родительские элементы элемента, отправившего событие, включая родительские элементы во внешних корнях shadow DOM. Если событие является composed, но bubble, его могут получить только элемент, отправивший событие, и элемент-хост, содержащий корень shadow DOM.
Обратите внимание: большинство стандартных событий пользовательского интерфейса, в том числе все события мыши, сенсорного ввода и клавиатуры, одновременно всплывают и являются составными. Дополнительную информацию см. в документации MDN о составных событиях.
Понимание переназначения цели события
Составные события, отправленные из корня shadow DOM, переназначаются: для любого обработчика на элементе, содержащем корень shadow DOM, или на одном из его родительских элементов они выглядят так, будто исходят от элемента-хоста. Поскольку компоненты Lit отображаются в корнях shadow DOM, все составные события, отправленные из компонента Lit, выглядят так, будто их отправил сам компонент Lit. Свойство target события содержит компонент Lit.
<my-element onClick="(e) => console.log(e.target)"></my-element>
render() {
return html`
<button id="mybutton" @click="${(e) => console.log(e.target)}">
click me
</button>`;
}
В особых случаях, когда необходимо определить источник события, используйте API event.composedPath(). Этот метод возвращает массив всех узлов, через которые прошло событие, включая узлы внутри корней shadow DOM. Поскольку это нарушает инкапсуляцию, не следует полагаться на детали реализации, которые могут оказаться доступными. Один из распространённых вариантов использования — определение того, был ли нажат элемент-якорь, например для клиентской маршрутизации.
handleMyEvent(event) {
console.log('Origin: ', event.composedPath()[0]);
}
Дополнительную информацию см. в документации MDN по composedPath.
Обмен данными между отправителем события и обработчиком
События предназначены прежде всего для передачи изменений от отправителя события обработчику, но с их помощью также можно передавать информацию от обработчика отправителю.
Для этого можно предоставить в событиях API, которое обработчики смогут использовать для настройки поведения компонента. Например, обработчик может задать свойство в свойстве detail пользовательского события, а отправляющий событие компонент затем использует его для настройки поведения.
Ещё один способ обмена данными между отправителем и обработчиком — метод preventDefault(). Его можно вызвать, чтобы указать, что стандартное действие события не должно выполняться. Когда обработчик вызывает preventDefault(), свойство defaultPrevented события становится равным true. Затем обработчик может использовать этот флаг для настройки поведения.
Оба этих приёма используются в следующем примере:
© Google LLC
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://lit.dev/docs/v2/components/events/