Реактивные контроллеры
Реактивный контроллер — это объект, который может подключаться к циклу реактивного обновления компонента. Контроллеры могут объединять состояние и поведение, связанные с определённой функцией, и тем самым позволять повторно использовать их в нескольких определениях компонентов.
С помощью контроллеров можно реализовывать функции, которым требуются собственное состояние и доступ к жизненному циклу компонента, например:
- Обработку глобальных событий, таких как события мыши
- Управление асинхронными задачами, например получение данных по сети
- Запуск анимаций
Реактивные контроллеры позволяют создавать компоненты, комбинируя более мелкие части, которые сами по себе не являются компонентами. Их можно считать повторно используемыми частичными определениями компонентов с собственной идентичностью и состоянием.
Реактивные контроллеры во многом похожи на примеси классов. Главное отличие в том, что у них есть собственная идентичность и они не добавляются в прототип компонента. Это помогает изолировать их 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(), который может отображать один из четырёх шаблонов в зависимости от состояния задачи. Логика задачи и способ обновления хоста скрыты от хост-компонента.
НУЖНО СДЕЛАТЬ
- Анимации
См. также
© Google LLC
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://lit.dev/docs/composition/controllers/