Spec-Zone.ru › Lit 3

События

События — стандартный способ, с помощью которого элементы сообщают об изменениях. Обычно эти изменения происходят в результате взаимодействия с пользователем. Например, кнопка отправляет событие щелчка, когда пользователь нажимает на нее; поле ввода отправляет событие изменения, когда пользователь вводит в него значение.

Помимо этих стандартных событий, которые отправляются автоматически, элементы 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. Полное описание см. в документации MDN по методу EventTarget.addEventListener().

Конструктор компонента — подходящее место для добавления обработчиков событий к компоненту.

constructor() {
  super();
  this.addEventListener('click', (e) => console.log(e.type, e.target.localName));
}

Добавление обработчиков событий непосредственно к компоненту — это форма делегирования событий, которая помогает сократить объем кода или повысить производительность. Подробности см. в разделе делегирование событий. Обычно при таком подходе свойство события target используют, чтобы выполнить действие в зависимости от элемента, отправившего событие.

Однако события, отправленные из shadow DOM компонента, перенаправляются при обработке обработчиком событий компонента. Это означает, что целью события становится сам компонент. Дополнительные сведения см. в разделе Работа с событиями в shadow DOM.

Перенаправление может мешать делегированию событий. Чтобы избежать этого, обработчики событий можно добавить непосредственно к теневому корню компонента. Поскольку shadowRoot недоступен в constructor, обработчики событий можно добавить в методе createRenderRoot следующим образом. Обратите внимание: важно убедиться, что метод 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.

Полное описание отправки событий см. в документации MDN по методу EventTarget.dispatchEvent().

Когда отправлять событие

События следует отправлять в ответ на взаимодействие с пользователем или асинхронные изменения состояния компонента. Как правило, их не следует отправлять в ответ на изменение состояния, которое владелец компонента выполняет через API свойств или атрибутов. Именно так обычно работают встроенные элементы веб-платформы.

Например, когда пользователь вводит значение в элемент input, отправляется событие change, но если код задает свойство value элемента input, событие change не отправляется.

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

Обычно это означает, что компонент должен отправлять событие в ответ на другое событие, которое он прослушивает.

Отправка событий после обновления элемента

Часто событие следует отправлять только после обновления и отображения элемента. Это может быть необходимо, если событие должно сообщить об изменении отображаемого состояния в ответ на взаимодействие с пользователем. В таком случае после изменения состояния, но перед отправкой события, можно дождаться Promise updateComplete компонента.

Использование стандартных или пользовательских событий

События можно отправлять, создав экземпляр Event или CustomEvent. Оба подхода подходят. При использовании CustomEvent все данные события передаются в свойстве detail события. При использовании Event можно создать подкласс события и добавить в него пользовательский API.

Подробности о создании событий см. в документации MDN по Event.

Отправка пользовательского события:

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, задайте свойству 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, его могут получить все родительские элементы элемента, отправившего событие, включая родительские элементы во внешних теневых корнях. Если событие является composed, но не bubble, его могут получить только элемент, отправивший событие, и хост-элемент, содержащий теневой корень.

Обратите внимание: большинство стандартных событий пользовательского интерфейса, включая все события мыши, касания и клавиатуры, одновременно всплывают и являются составными. Дополнительные сведения см. в документации MDN о составных событиях.

Понимание перенаправления событий

Составные события, отправленные внутри теневого корня, перенаправляются. Это означает, что для обработчика на элементе, содержащем теневой корень, или на любом из его родительских элементов кажется, будто событие отправлено этим элементом. Поскольку компоненты Lit отображаются в теневых корнях, все составные события, отправленные внутри компонента 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(). Этот метод возвращает массив всех узлов, через которые прошло событие, включая узлы внутри теневых корней. Поскольку это нарушает инкапсуляцию, следует избегать зависимости от деталей реализации, которые могут стать доступными. Типичный пример использования — определение того, был ли щелчок выполнен по элементу-ссылке, для клиентской маршрутизации.

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/components/events/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API