Spec-Zone.ru › Lit 3

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

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

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

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

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

Реактивные контроллеры во многом похожи на примеси классов. Главное отличие в том, что у них есть собственная идентичность и они не добавляются в прототип компонента. Это помогает изолировать их 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, поэтому к этому моменту корневой элемент Shadow 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. Ниже перечислены некоторые распространённые варианты использования.

Внешние входные данные

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

Пример: MouseMoveController

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

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

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

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

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

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

НУЖНО СДЕЛАТЬ

  • Анимации

См. также

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

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

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

Spec-Zone.ru

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