Spec-Zone.ru › Lit 2

Реактивные контроллеры

В Lit 2 появилась новая концепция повторного использования и компоновки кода, называемая реактивными контроллерами.

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

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

  • Обработка глобальных событий, таких как события мыши
  • Управление асинхронными задачами, например получение данных по сети
  • Запуск анимаций

Реактивные контроллеры позволяют создавать компоненты, объединяя небольшие части, которые сами по себе не являются компонентами. Их можно рассматривать как повторно используемые частичные определения компонентов с собственными идентификаторами и состоянием.

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

Использование контроллера

У каждого контроллера есть собственный API для создания, но обычно вы создаёте экземпляр и сохраняете его в компоненте:

class MyElement extends LitElement {
  private clock = new ClockController(this, 1000);
}

Компонент, связанный с экземпляром контроллера, называется хост-компонентом.

Экземпляр контроллера регистрируется для получения обратных вызовов жизненного цикла от хост-компонента и инициирует обновление хоста, когда у контроллера появляются новые данные для отображения. Так пример ClockController периодически выводит текущее время.

Обычно контроллер предоставляет функциональность, которую можно использовать в методе render() хоста. Например, многие контроллеры имеют состояние, например текущее значение:

  render() {
    return html`
      <div>Current time: ${this.clock.value}</div>
    `;
  }

Поскольку у каждого контроллера собственный API, обратитесь к документации конкретного контроллера, чтобы узнать, как его использовать.

Создание контроллера

Реактивный контроллер — это объект, связанный с хост-компонентом, который реализует один или несколько обратных вызовов жизненного цикла хоста либо взаимодействует с ним. Его можно реализовать разными способами, но мы сосредоточимся на использовании классов JavaScript с конструкторами для инициализации и методами для обработки событий жизненного цикла.

Инициализация контроллера

Контроллер регистрируется в хост-компоненте, вызывая host.addController(this). Обычно контроллер сохраняет ссылку на хост-компонент, чтобы взаимодействовать с ним позднее.

class ClockController implements ReactiveController {
  private host: ReactiveControllerHost;

  constructor(host: ReactiveControllerHost) {
    // Store a reference to the host
    this.host = host;
    // Register for lifecycle updates
    host.addController(this);
  }
}
class ClockController {
  constructor(host) {
    // Store a reference to the host
    this.host = host;
    // Register for lifecycle updates
    host.addController(this);
  }
}

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

class ClockController implements ReactiveController {
  private host: ReactiveControllerHost;
  timeout: number

  constructor(host: ReactiveControllerHost, timeout: number) {
    this.host = host;
    this.timeout = timeout;
    host.addController(this);
  }
class ClockController {
  constructor(host, timeout) {
    this.host = host;
    this.timeout = timeout;
    host.addController(this);
  }

После регистрации контроллера в хост-компоненте можно добавить в него обратные вызовы жизненного цикла, а также другие поля и методы класса для реализации нужного состояния и поведения.

Жизненный цикл

Жизненный цикл реактивного контроллера, определённый в интерфейсе ReactiveController, представляет собой подмножество цикла реактивного обновления. LitElement вызывает методы установленных контроллеров во время обратных вызовов своего жизненного цикла. Эти обратные вызовы необязательны.

  • hostConnected():
    • Вызывается, когда хост подключается.
    • Вызывается после создания renderRoot, поэтому к этому моменту корень теневого DOM уже существует.
    • Полезен для настройки прослушивателей событий, наблюдателей и т. д.
  • hostUpdate():
    • Вызывается перед методами update() и render() хоста.
    • Полезен для чтения DOM до его обновления (например, для анимации).
  • hostUpdated():
    • Вызывается после обновлений, перед методом updated() хоста.
    • Полезен для чтения DOM после его изменения (например, для анимации).
  • hostDisconnected():
    • Вызывается, когда хост отключается.
    • Полезен для очистки ресурсов, созданных в hostConnected(), например прослушивателей событий и наблюдателей.

Дополнительную информацию см. в разделе Цикл реактивного обновления.

API хоста контроллера

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

Минимальный API хоста контроллера:

  • addController(controller: ReactiveController)
  • removeController(controller: ReactiveController)
  • requestUpdate()
  • updateComplete: Promise<boolean>

Можно также создавать контроллеры, предназначенные для HTMLElement, ReactiveElement, LitElement и требующие большего количества этих API; или даже контроллеры, связанные с классом определённого элемента или другим интерфейсом.

LitElement и ReactiveElement являются хостами контроллеров, но хостами могут быть и другие объекты, например базовые классы из других библиотек веб-компонентов, компоненты фреймворков или другие контроллеры.

Создание контроллеров на основе других контроллеров

Контроллеры также можно составлять из других контроллеров. Для этого создайте дочерний контроллер и передайте ему хост.

class DualClockController implements ReactiveController {
  private clock1: ClockController;
  private clock2: ClockController;

  constructor(host: ReactiveControllerHost, delay1: number, delay2: number) {
    this.clock1 = new ClockController(host, delay1);
    this.clock2 = new ClockController(host, delay2);
  }

  get time1() { return this.clock1.value; }
  get time2() { return this.clock2.value; }
}
class DualClockController {
  constructor(host, delay1, delay2) {
    this.clock1 = new ClockController(host, delay1);
    this.clock2 = new ClockController(host, delay2);
  }

  get time1() { return this.clock1.value; }
  get time2() { return this.clock2.value; }
}

Контроллеры и директивы

Сочетание контроллеров и директив может быть очень эффективным приёмом, особенно для директив, которым нужно выполнять работу до или после отрисовки, например директив анимации; или для контроллеров, которым нужны ссылки на определённые элементы шаблона.

Существует два основных способа использования контроллеров с директивами:

  • Директивы-контроллеры. Это директивы, которые сами являются контроллерами, чтобы подключаться к жизненному циклу хоста.
  • Контроллеры, владеющие директивами. Это контроллеры, которые создают одну или несколько директив для использования в шаблоне хоста.

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

Директивы-контроллеры

Реактивные контроллеры необязательно хранить в полях экземпляра хоста. Любой объект, добавленный в хост с помощью addController(), является контроллером. В частности, контроллером может быть и директива. Это позволяет директиве подключаться к жизненному циклу хоста.

Контроллеры, владеющие директивами

Директивы не обязательно должны быть самостоятельными функциями: они также могут быть методами других объектов, например контроллеров. Это может быть полезно, когда контроллеру нужна конкретная ссылка на элемент шаблона.

Например, представим ResizeController, который позволяет наблюдать за размером элемента с помощью ResizeObserver. Для этого нам понадобятся экземпляр ResizeController и директива, размещённая на элементе, за которым мы хотим наблюдать:

class MyElement extends LitElement {
  private _textSize = new ResizeController(this);

  render() {
    return html`
      <textarea ${this._textSize.observe()}></textarea>
      <p>The width is ${this._textSize.contentRect?.width}</p>
    `;
  }
}
class MyElement extends LitElement {
  _textSize = new ResizeController(this);

  render() {
    return html`
      <textarea ${this._textSize.observe()}></textarea>
      <p>The width is ${this._textSize.contentRect?.width}</p>
    `;
  }
}

Чтобы реализовать это, создайте директиву и вызывайте её из метода:

class ResizeDirective {
  /* ... */
}
const resizeDirective = directive(ResizeDirective);

export class ResizeController {
  /* ... */
  observe() {
    // Pass a reference to the controller so the directive can
    // notify the controller on size changes.
    return resizeDirective(this);
  }
}

ЗАДАЧА

  • Проверить и доработать этот пример

Сценарии использования

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

Внешние источники данных

Реактивные контроллеры можно использовать для подключения к внешним источникам данных. Например, к событиям клавиатуры и мыши, наблюдателям за изменениями размера или DOM. Контроллер может предоставлять текущее значение входных данных для отображения и запрашивать обновление хоста при изменении значения.

Пример: MouseMoveController

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

Асинхронные задачи

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

Контроллеры отлично подходят для объединения выполнения задачи и её состояния, что упрощает использование задачи внутри компонента. У задачи, реализованной как контроллер, обычно есть входные данные, которые может задавать хост, и выходные данные, которые хост может отображать.

@lit-labs/task содержит универсальный контроллер Task, который может получать входные данные от хоста, выполнять функцию задачи и отображать разные шаблоны в зависимости от состояния задачи.

С помощью Task можно создать пользовательский контроллер с API, настроенным под конкретную задачу. Здесь мы оборачиваем Task в NamesController, который может получать один из указанных имён из демонстрационного REST API. NameController предоставляет свойство kind в качестве входных данных и метод render(), который может отображать один из четырёх шаблонов в зависимости от состояния задачи. Логика задачи и способ обновления хоста абстрагированы от хост-компонента.

ЗАДАЧА

  • Анимации

См. также

  • Цикл реактивного обновления
  • @lit-labs/task

Изменить эту страницу

© Google LLC
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://lit.dev/docs/v2/composition/controllers/

Spec-Zone.ru

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