Перейти к содержанию

События

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

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

Любой код Javascript, включая сами элементы Lit, может прослушивать события и выполнять действия на их основе. Например, элемент панели инструментов может фильтровать список при выборе пункта меню; элемент входа в систему может обрабатывать вход в систему при нажатии на кнопку входа.

Прослушивание событий

В дополнение к стандартному API addEventListener, Lit предлагает декларативный способ добавления слушателей событий.

Добавление слушателей событий в шаблоне элемента

Вы можете использовать выражения @ в своем шаблоне, чтобы добавить слушателей событий в элементы шаблона компонента. Декларативные слушатели событий добавляются при отрисовке шаблона.

Настройка параметров слушателя событий

Если вам нужно настроить параметры события, используемые для декларативного слушателя событий (например, passive или capture), вы можете указать их в слушателе с помощью декоратора @eventOptions. Объект, переданный в @eventOptions, передается в качестве параметра options в addEventListener.

1
2
3
4
5
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().

1
2
3
4
5
6
render() {
  return html`<button @click=${{
    handleEvent: () => this.onClick(),
    once: true,
  }}>click</button>`
}

Добавление слушателей событий в компонент или его теневой корень

Чтобы получать уведомления о событиях, отправляемых от дочерних элементов компонента, а также дочерних элементов, выводимых в теневой DOM через шаблон компонента, вы можете добавить слушателя в сам компонент с помощью стандартного DOM-метода addEventListener. Подробности см. в EventTarget.addEventListener() на MDN.

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

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

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

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

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

Добавление слушателей событий к другим элементам

Если ваш компонент добавляет слушателя событий к чему-либо, кроме себя или своего шаблона DOM — например, к Window, Document или какому-либо элементу в основном DOM — вы должны добавить слушателя в connectedCallback и удалить его в disconnectedCallback.

  • Удаление слушателя событий в disconnectedCallback гарантирует, что вся память, выделенная вашим компонентом, будет очищена, когда ваш компонент будет уничтожен или отключен от страницы.

  • Добавление слушателя событий в connectedCallback (вместо, например, конструктора или firstUpdated) гарантирует, что ваш компонент будет заново создавать свой слушатель событий, если он будет отключен и впоследствии снова подключен к DOM.

1
2
3
4
5
6
7
8
connectedCallback() {
  super.connectedCallback();
  window.addEventListener('resize', this._handleResize);
}
disconnectedCallback() {
  window.removeEventListener('resize', this._handleResize);
  super.disconnectedCallback();
}

Дополнительные сведения о connectedCallback и disconnectedCallback см. в документации MDN по использованию пользовательских элементов lifecycle callbacks.

Оптимизация для производительности

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

Делегирование событий

Делегирование событий позволяет уменьшить количество используемых слушателей событий и, следовательно, повысить производительность. Также иногда удобно централизовать обработку событий, чтобы сократить объем кода. Делегирование событий можно использовать только для обработки событий, которые пузырятся. Подробности о пузырьках см. в разделе Диспетчеризация событий.

Пузырящиеся события могут быть услышаны на любом элементе-предке в DOM. Вы можете воспользоваться этим преимуществом, добавив единственный слушатель событий на компонент-предок, чтобы получать уведомления о событии пузырения, отправленном любым из его потомков в DOM. Используйте свойство target события, чтобы предпринять конкретные действия в зависимости от элемента, отправившего событие.

Асинхронное добавление слушателей событий

Чтобы добавить слушателя событий после рендеринга, используйте метод firstUpdated. Это обратный вызов жизненного цикла Lit, который запускается после того, как компонент впервые обновляет и рендерит свой шаблон DOM.

Обратный вызов firstUpdated срабатывает после первого обновления компонента и вызова его метода render, но до того, как браузер успел отрисовать.

Дополнительную информацию см. в разделе firstUpdated документации Lifecycle.

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

1
2
3
4
5
async firstUpdated() {
  // Give the browser a chance to paint
  await new Promise((r) => setTimeout(r, 0));
  this.addEventListener('click', this._handleClick);
}

Понимание this в слушателях событий

Слушатели событий, добавленные с помощью декларативного синтаксиса @ в шаблоне, автоматически привязываются к компоненту.

Поэтому вы можете использовать this для ссылки на ваш экземпляр компонента в любом декларативном обработчике событий:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class MyElement extends LitElement {
    render() {
        return html`<button @click="${this._handleClick}">
            click
        </button>`;
    }
    _handleClick(e) {
        console.log(this.prop);
    }
}

При императивном добавлении слушателей с помощью addEventListener необходимо использовать функцию-стрелку, чтобы this ссылался на компонент:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
export class MyElement extends LitElement {
    private _handleResize = () => {
        // `this` refers to the component
        console.log(this.isConnected);
    };

    constructor() {
        window.addEventListener(
            'resize',
            this._handleResize,
        );
    }
}

Дополнительную информацию см. в документации по this на MDN.

Прослушивание событий, запускаемых из повторяющихся шаблонов

При прослушивании событий на повторяющихся элементах часто удобно использовать делегирование событий, если событие пузырится. Если же событие не пузырится, можно добавить слушателя на повторяющиеся элементы. Вот пример использования обоих методов:

Удаление слушателей событий

Передача null, undefined или nothing в выражение @ приведет к удалению любого существующего слушателя.

Диспетчеризация событий

Все узлы DOM могут отправлять события с помощью метода dispatchEvent. Сначала создайте экземпляр события, указав его тип и параметры. Затем передайте его в dispatchEvent следующим образом:

1
2
3
4
5
const event = new Event('my-event', {
    bubbles: true,
    composed: true,
});
myElement.dispatchEvent(event);

Опция bubbles позволяет событию распространяться вверх по дереву DOM к предкам отправляющего элемента. Важно установить этот флаг, если вы хотите, чтобы событие могло участвовать в делегировании событий.

Опцию composed полезно установить, чтобы позволить событию быть отправленным выше теневого DOM-дерева, в котором существует элемент.

Дополнительную информацию см. в разделе Работа с событиями в теневом DOM.

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

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

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

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

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

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

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

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

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

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

Подробности о создании событий см. в Event на MDN.

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

1
2
3
4
5
6
const event = new CustomEvent('my-event', {
    detail: {
        message: 'Something important happened',
    },
});
this.dispatchEvent(event);

Дополнительные сведения см. в документации MDN по пользовательским событиям.

Вызов стандартного события:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class MyEvent extends Event {
    constructor(message) {
        super();
        this.type = 'my-event';
        this.message = message;
    }
}

const event = new MyEvent('Something important happened');
this.dispatchEvent(event);

Работа с событиями в теневом DOM

При использовании теневого DOM есть несколько модификаций стандартной системы событий, которые важно понимать. Теневой DOM существует в первую очередь для того, чтобы обеспечить механизм определения масштаба в DOM, который инкапсулирует детали об этих "теневых" элементах. Как таковые, события в теневом DOM инкапсулируют определенные детали из внешних элементов DOM.

Понимание диспетчеризации составленных событий

По умолчанию событие, отправленное внутри теневого корня, не будет видно вне этого теневого корня. Чтобы событие проходило через границы теневого DOM, необходимо установить свойство composed в true. Обычно используется пара composed с bubbles, чтобы все узлы в дереве DOM могли видеть событие:

1
2
3
4
5
6
7
_dispatchMyEvent() {
  let myEvent = new CustomEvent('my-event', {
    detail: { message: 'my-event happened.' },
    bubbles: true,
    composed: true });
  this.dispatchEvent(myEvent);
}

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

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

Понимание ретаргетинга событий

Composed события, отправленные из теневого корня, ретаргетируются, то есть для любого слушателя элемента, содержащего теневой корень или любого из его предков, они кажутся исходящими от содержащего элемента. Поскольку Lit-компоненты рендерятся в теневые корни, все составленные события, отправленные изнутри Lit-компонента, выглядят так, будто они отправлены самим Lit-компонентом. Свойством target события является компонент Lit.

1
2
3
<my-element
    onClick="(e) => console.log(e.target)"
></my-element>

1
2
3
4
5
6
render() {
  return html`
    <button id="mybutton" @click="${(e) => console.log(e.target)}">
      click me
    </button>`;
}

В сложных случаях, когда требуется определить происхождение события, используйте API event.composedPath(). Этот метод возвращает массив всех узлов, пройденных диспетчером события, включая узлы в теневых корнях. Поскольку при этом нарушается инкапсуляция, следует позаботиться о том, чтобы не полагаться на детали реализации, которые могут быть раскрыты. К распространенным случаям использования относится определение того, был ли элемент, на котором щелкнули, тегом якоря, для целей маршрутизации на стороне клиента.

1
2
3
handleMyEvent(event) {
  console.log('Origin: ', event.composedPath()[0]);
}

Дополнительные сведения см. в MDN документации по composedPath.

Общение между диспетчером событий и слушателем

События существуют в основном для передачи изменений от диспетчера событий к слушателю, но события также можно использовать для передачи информации от слушателя обратно диспетчеру.

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

Еще один способ взаимодействия между диспетчером и слушателем — метод preventDefault(). Он может быть вызван, чтобы указать, что стандартное действие события не должно происходить. Когда слушатель вызывает preventDefault(), свойство события defaultPrevented становится истинным. Этот флаг может быть использован слушателем для настройки поведения.

Обе эти техники используются в следующем примере:

Комментарии