Жизненный цикл
Компоненты Lit используют стандартные методы жизненного цикла пользовательских элементов. Кроме того, Lit вводит реактивный цикл обновления, который отображает изменения в DOM при изменении реактивных свойств.
Стандартный жизненный цикл пользовательского элемента
Компоненты Lit — это стандартные пользовательские элементы, которые наследуют методы жизненного цикла пользовательского элемента. Информацию о жизненном цикле пользовательских элементов см. в статье MDN Использование колбэков жизненного цикла.
Если вам нужно настроить какой-либо из стандартных методов жизненного цикла пользовательского элемента, обязательно вызовите реализацию super (например, super.connectedCallback()), чтобы сохранить стандартную функциональность Lit.
constructor()
Вызывается при создании элемента. Кроме того, он вызывается при обновлении существующего элемента, что происходит, когда определение пользовательского элемента загружается после того, как элемент уже находится в DOM.
Поведение Lit
Запрашивает асинхронное обновление с помощью метода requestUpdate(), поэтому при обновлении компонента Lit он сразу же выполняет обновление.
Сохраняет все свойства, которые уже были заданы для элемента. Это гарантирует, что значения, заданные до обновления, сохранятся и корректно переопределят значения по умолчанию, заданные компонентом.
Варианты использования
Выполняйте однократные задачи инициализации, которые необходимо выполнить до первого обновления. Например, если не используются декораторы, значения свойств по умолчанию можно задать в конструкторе, как показано в разделе Объявление свойств в статическом поле properties.
constructor() {
super();
this.foo = 'foo';
this.bar = 'bar';
}
connectedCallback()
Вызывается при добавлении компонента в DOM документа.
Поведение Lit
Lit запускает первый цикл обновления элемента после его подключения. При подготовке к отображению Lit также создает renderRoot (обычно его shadowRoot).
После того как элемент хотя бы один раз был подключен к документу, обновления компонента продолжаются независимо от состояния подключения элемента.
Варианты использования
В connectedCallback() следует настраивать задачи, которые должны выполняться только тогда, когда элемент подключен к документу. Чаще всего это добавление обработчиков событий к узлам за пределами элемента, например обработчика события keydown для window. Как правило, все действия, выполняемые в connectedCallback(), следует отменять при отключении элемента — например, удалять обработчики событий с window, чтобы избежать утечек памяти.
connectedCallback() {
super.connectedCallback()
window.addEventListener('keydown', this._handleKeydown);
}
disconnectedCallback()
Вызывается при удалении компонента из DOM документа.
Поведение Lit
Приостанавливает реактивный цикл обновления. Он возобновляется после подключения элемента.
Варианты использования
Этот колбэк — основной сигнал для элемента о том, что он может больше не использоваться; поэтому disconnectedCallback() должен гарантировать, что ничто не удерживает ссылку на элемент (например, обработчики событий, добавленные к узлам за пределами элемента), чтобы сборщик мусора мог освободить его. Поскольку элементы могут быть снова подключены после отключения, например при перемещении элемента в DOM или кэшировании, такие ссылки или обработчики может потребоваться восстановить с помощью connectedCallback(), чтобы элемент продолжал работать в этих сценариях. Например, удаляйте обработчики событий с узлов за пределами элемента, например обработчик события keydown, добавленный для window.
disconnectedCallback() {
super.disconnectedCallback()
window.removeEventListener('keydown', this._handleKeydown);
}
Внутренние обработчики событий удалять не нужно. Не нужно удалять обработчики событий, добавленные в собственный DOM компонента, в том числе декларативно заданные в шаблоне. В отличие от внешних обработчиков событий, они не препятствуют сборке компонента мусора.
attributeChangedCallback()
Вызывается при изменении одного из observedAttributes элемента.
Поведение Lit
Lit использует этот колбэк для синхронизации изменений атрибутов с реактивными свойствами. В частности, при установке атрибута устанавливается соответствующее свойство. Lit также автоматически настраивает массив observedAttributes элемента в соответствии со списком реактивных свойств компонента.
Варианты использования
Обычно реализовывать этот колбэк не требуется.
adoptedCallback()
Вызывается при перемещении компонента в новый документ.
Обратите внимание, что adoptedCallback не полифилится.
Поведение Lit
По умолчанию Lit не выполняет никаких действий в этом колбэке.
Варианты использования
Этот колбэк следует использовать только в сложных сценариях, когда поведение элемента должно меняться при смене документа.
Реактивный цикл обновления
Помимо стандартного жизненного цикла пользовательского элемента, компоненты Lit реализуют реактивный цикл обновления.
Реактивный цикл обновления запускается при изменении реактивного свойства или явном вызове метода requestUpdate(). Lit выполняет обновления асинхронно, поэтому изменения свойств объединяются: если после запроса обновления, но до его начала, изменяются другие свойства, все изменения учитываются в одном обновлении.
Обновления выполняются во время микрозадач, то есть до того, как браузер отобразит следующий кадр на экране. Подробнее о времени выполнения в браузере см. в статье Джейка Арчибальда о микрозадачах.
В общих чертах реактивный цикл обновления выглядит так:
- Обновление планируется при изменении одного или нескольких свойств или при вызове
requestUpdate(). - Обновление выполняется до отображения следующего кадра.
- Устанавливаются отражаемые атрибуты.
- Вызывается метод render компонента для обновления его внутреннего DOM.
- Обновление завершается, и промис
updateCompleteразрешается.
Более подробно цикл выглядит так:
Перед обновлением
Обновление
После обновления
Карта changedProperties
Многие методы реактивного обновления получают Map измененных свойств. Ключи Map — это имена свойств, а их значения — предыдущие значения свойств. Текущие значения свойств всегда можно получить с помощью this.property или this[property].
Типы TypeScript для changedProperties
Если вы используете TypeScript и хотите обеспечить строгую проверку типов для карты changedProperties, используйте PropertyValues<this>, которая выводит правильный тип для каждого имени свойства.
import {LitElement, html, PropertyValues} from 'lit';
...
shouldUpdate(changedProperties: PropertyValues<this>) {
...
}
Если строгая типизация для вас не так важна или вы проверяете только имена свойств, а не предыдущие значения, можно использовать менее строгий тип, например Map<string, any>.
Обратите внимание, что PropertyValues<this> не распознает свойства protected и private. Если вы проверяете свойства protected или private, потребуется менее строгий тип.
Изменение свойств во время обновления
Изменение свойства во время обновления (вплоть до вызова метода render() включительно) обновляет карту changedProperties, но не запускает новое обновление. Изменение свойства после render() (например, в методе updated()) запускает новый цикл обновления, а измененное свойство добавляется в новую карту changedProperties для следующего цикла.
Запуск обновления
Обновление запускается при изменении реактивного свойства или вызове метода requestUpdate(). Поскольку обновления выполняются асинхронно, все изменения, произошедшие до выполнения обновления, объединяются в одно обновление.
hasChanged()
Вызывается при установке реактивного свойства. По умолчанию hasChanged() выполняет проверку строгого равенства; если она возвращает true, планируется обновление. Подробнее см. в разделе Настройка hasChanged().
requestUpdate()
Вызовите requestUpdate(), чтобы явно запланировать обновление. Это может быть полезно, если элемент должен обновиться и отобразить изменения, не связанные с изменением свойства. Например, компонент таймера может вызывать requestUpdate() каждую секунду.
connectedCallback() {
super.connectedCallback();
this._timerInterval = setInterval(() => this.requestUpdate(), 1000);
}
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this._timerInterval);
}
Список измененных свойств хранится в карте changedProperties, передаваемой последующим методам жизненного цикла. Ключи карты — это имена свойств, а их значения — предыдущие значения свойств.
При вызове requestUpdate() можно передать имя свойства и его предыдущее значение; они будут сохранены в карте changedProperties. Это может быть полезно, если вы реализуете для свойства собственные геттер и сеттер. Подробнее о реализации пользовательских геттеров и сеттеров см. в разделе Реактивные свойства.
this.requestUpdate('state', this._previousState);
Выполнение обновления
При выполнении обновления вызывается метод performUpdate(). Этот метод вызывает ряд других методов жизненного цикла.
Изменения, которые обычно запускают обновление, но происходят во время обновления компонента, не планируют новое обновление. Это позволяет вычислять значения свойств в процессе обновления. Свойства, измененные во время обновления, отражаются в карте changedProperties, поэтому последующие методы жизненного цикла могут учитывать эти изменения.
shouldUpdate()
Вызывается для определения необходимости цикла обновления.
| Аргументы |
changedProperties: Map с именами измененных свойств в качестве ключей и соответствующими предыдущими значениями в качестве значений. |
| Запускает обновление? | Нет. Изменения свойств внутри этого метода не запускают обновление элемента. |
| Нужно ли вызывать super? | Не требуется. |
| Вызывается на сервере? | Нет. |
Если shouldUpdate() возвращает true, что происходит по умолчанию, обновление выполняется обычным образом. Если возвращается false, оставшаяся часть цикла обновления не вызывается, но промис updateComplete все равно разрешается.
Реализуйте shouldUpdate(), чтобы указать, какие изменения свойств должны запускать обновления. Используйте карту changedProperties для сравнения текущих и предыдущих значений.
shouldUpdate(changedProperties: Map<string, any>) {
// Only update element if prop1 changed.
return changedProperties.has('prop1');
}
shouldUpdate(changedProperties) {
// Only update element if prop1 changed.
return changedProperties.has('prop1');
}willUpdate()
Вызывается перед update() для вычисления значений, необходимых во время обновления.
| Аргументы |
changedProperties: Map с именами измененных свойств в качестве ключей и соответствующими предыдущими значениями в качестве значений. |
| Запускает обновление? | Нет. Изменения свойств внутри этого метода не запускают обновление элемента. |
| Нужно ли вызывать super? | Не требуется. |
| Вызывается на сервере? | Да. |
Реализуйте willUpdate() для вычисления значений свойств, зависящих от других свойств и используемых в остальной части процесса обновления.
willUpdate(changedProperties: PropertyValues<this>) {
// only need to check changed properties for an expensive computation.
if (changedProperties.has('firstName') || changedProperties.has('lastName')) {
this.sha = computeSHA(`${this.firstName} ${this.lastName}`);
}
}
render() {
return html`SHA: ${this.sha}`;
}
willUpdate(changedProperties) {
// only need to check changed properties for an expensive computation.
if (changedProperties.has('firstName') || changedProperties.has('lastName')) {
this.sha = computeSHA(`${this.firstName} ${this.lastName}`);
}
}
render() {
return html`SHA: ${this.sha}`;
}update()
Вызывается для обновления DOM компонента.
| Аргументы |
changedProperties: Map с именами измененных свойств в качестве ключей и соответствующими предыдущими значениями в качестве значений. |
| Запускает обновление? | Нет. Изменения свойств внутри этого метода не запускают обновление элемента. |
| Нужно ли вызывать super? | Да. Без вызова super атрибуты и шаблон элемента не будут обновлены. |
| Вызывается на сервере? | Нет. |
Отражает значения свойств в атрибутах и вызывает render() для обновления внутреннего DOM компонента.
Как правило, реализовывать этот метод не нужно.
render()
Вызывается методом update() и должен быть реализован так, чтобы возвращать результат, пригодный для отображения (например, TemplateResult), который используется для отображения DOM компонента.
| Аргументы | Нет. |
| Запускает обновление? | Нет. Изменения свойств внутри этого метода не запускают обновление элемента. |
| Нужно ли вызывать super? | Не требуется. |
| Вызывается на сервере? | Да. |
Метод render() не принимает аргументов, но обычно обращается к свойствам компонента. Подробнее см. в разделе Отображение.
render() {
const header = `<header>${this.header}</header>`;
const content = `<section>${this.content}</section>`;
return html`${header}${content}`;
}
Завершение обновления
После вызова update() для отображения изменений в DOM компонента можно выполнять действия с DOM компонента с помощью следующих методов.
firstUpdated()
Вызывается после первого обновления DOM компонента, непосредственно перед вызовом updated().
| Аргументы |
changedProperties: Map с именами измененных свойств в качестве ключей и соответствующими предыдущими значениями в качестве значений. |
| Запускает обновление? | Да. Изменения свойств внутри этого метода планируют новый цикл обновления. |
| Нужно ли вызывать super? | Не требуется. |
| Вызывается на сервере? | Нет. |
Реализуйте firstUpdated() для однократного выполнения действий после создания DOM компонента. Например, можно установить фокус на определенный отображенный элемент или добавить к элементу ResizeObserver или IntersectionObserver.
firstUpdated() {
this.renderRoot.getElementById('my-text-area').focus();
}
updated()
Вызывается после завершения обновления компонента и обновления и отображения DOM элемента.
| Аргументы |
changedProperties: Map с именами измененных свойств в качестве ключей и соответствующими предыдущими значениями в качестве значений. |
| Запускает обновление? | Да. Изменения свойств внутри этого метода запускают обновление элемента. |
| Нужно ли вызывать super? | Не требуется. |
| Вызывается на сервере? | Нет. |
Реализуйте updated() для выполнения задач, связанных с DOM элемента после обновления. Например, коду, выполняющему анимацию, может потребоваться измерить DOM элемента.
updated(changedProperties: Map<string, any>) {
if (changedProperties.has('collapsed')) {
this._measureDOM();
}
}
updated(changedProperties) {
if (changedProperties.has('collapsed')) {
this._measureDOM();
}
}updateComplete
Промис updateComplete разрешается после завершения обновления элемента. Используйте updateComplete, чтобы дождаться обновления. Разрешенное значение — логическое значение, указывающее, завершил ли элемент обновление. Оно будет true, если после завершения цикла обновления нет ожидающих обновлений.
При обновлении элемента могут обновляться и его дочерние элементы. По умолчанию промис updateComplete разрешается после завершения обновления элемента, не дожидаясь завершения обновлений дочерних элементов. Это поведение можно настроить, переопределив getUpdateComplete.
Существует несколько сценариев, в которых может потребоваться узнать, завершилось ли обновление элемента:
Тесты При написании тестов можно дождаться промиса
updateCompleteперед проверкой DOM компонента. Если проверки зависят от завершения обновлений всего дерева потомков компонента, часто лучше дождатьсяrequestAnimationFrame, поскольку планирование Lit по умолчанию использует очередь микрозадач браузера, которая очищается до начала кадров анимации. Это гарантирует, что все ожидающие обновления Lit на странице завершатся до вызова колбэкаrequestAnimationFrame.Измерение Некоторым компонентам может потребоваться измерять DOM для реализации определенных макетов. Хотя макеты всегда лучше реализовывать с помощью чистого CSS, а не измерений на JavaScript, иногда ограничения CSS делают это неизбежным. В очень простых случаях, если вы измеряете компоненты Lit или ReactiveElement, может быть достаточно дождаться
updateCompleteпосле изменения состояния и до измерения. Однако, посколькуupdateCompleteне дожидается обновления всех потомков, для запуска кода измерения при изменении макетов мы рекомендуем использоватьResizeObserverкак более надежный способ.-
События Рекомендуется отправлять события из компонентов после завершения отображения, чтобы обработчики событий видели полностью отображенное состояние компонента. Для этого перед отправкой события можно дождаться промиса
updateComplete.async _loginClickHandler() { this.loggedIn = true; // Wait for `loggedIn` state to be rendered to the DOM await this.updateComplete; this.dispatchEvent(new Event('login')); }
Промис updateComplete отклоняется, если во время цикла обновления возникает необработанная ошибка. Подробнее см. в разделе Обработка ошибок в цикле обновления.
Обработка ошибок в цикле обновления
Если в методе жизненного цикла, например render() или update(), возникает необработанное исключение, промис updateComplete отклоняется. Если код в методе жизненного цикла может вызвать исключение, рекомендуется поместить его в блок try/catch.
Если вы ожидаете промис updateComplete, можно также использовать try/catch:
try {
await this.updateComplete;
} catch (e) {
/* handle error */
}
В некоторых случаях ошибка может возникнуть в неожиданном месте. В качестве резервного решения можно добавить обработчик для window.onunhandledrejection, чтобы перехватывать такие ошибки. Например, можно использовать его для отправки сообщений об ошибках в серверную службу и диагностики проблем, которые трудно воспроизвести.
window.onunhandledrejection = function(e) {
/* handle error */
}
Дополнительная настройка
В этом разделе рассматриваются некоторые менее распространенные методы настройки цикла обновления.
scheduleUpdate()
Переопределите scheduleUpdate(), чтобы настроить время выполнения обновления. scheduleUpdate() вызывается непосредственно перед выполнением обновления и по умолчанию сразу вызывает performUpdate(). Переопределите его, чтобы отложить обновление: этот прием можно использовать, чтобы освободить основной поток отрисовки и обработки событий.
Например, следующий код планирует обновление после отображения следующего кадра, что может уменьшить подтормаживания, если обновление требует значительных ресурсов:
protected override async scheduleUpdate(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve));
super.scheduleUpdate();
}
async scheduleUpdate() {
await new Promise((resolve) => setTimeout(resolve));
super.scheduleUpdate();
}Если вы переопределяете scheduleUpdate(), вы должны самостоятельно вызвать super.scheduleUpdate() для выполнения ожидающего обновления.
performUpdate()
Реализует реактивный цикл обновления, вызывая другие методы, такие как shouldUpdate(), update() и updated().
Вызовите performUpdate(), чтобы немедленно обработать ожидающее обновление. Обычно это не требуется, но в редких случаях можно выполнить обновление синхронно. (Если ожидающего обновления нет, можно вызвать requestUpdate(), а затем performUpdate(), чтобы принудительно выполнить синхронное обновление.)
hasUpdated
Свойство hasUpdated возвращает true, если компонент обновлялся хотя бы один раз. В любом методе жизненного цикла можно использовать hasUpdated, чтобы выполнять действия, только если компонент еще не обновлялся.
getUpdateComplete()
Чтобы дождаться выполнения дополнительных условий перед разрешением промиса updateComplete, переопределите метод getUpdateComplete(). Например, может быть полезно дождаться обновления дочернего элемента. Сначала дождитесь super.getUpdateComplete(), а затем любого последующего состояния.
Рекомендуется переопределять метод getUpdateComplete(), а не геттер updateComplete, чтобы обеспечить совместимость с пользователями, использующими вывод TypeScript для ES5 (см. TypeScript#338).
class MyElement extends LitElement {
async getUpdateComplete() {
await super.getUpdateComplete();
await this._myChild.updateComplete;
}
}
Внешние хуки жизненного цикла: контроллеры и декораторы
Помимо реализации колбэков жизненного цикла в классах компонентов, внешний код, например декораторы, может нуждаться в подключении к жизненному циклу компонента.
Lit предлагает две концепции для интеграции внешнего кода с жизненным циклом реактивного обновления: static addInitializer() и addController():
static addInitializer()
addInitializer() позволяет коду, имеющему доступ к определению класса Lit, запускать код при создании экземпляров этого класса.
Это особенно полезно при написании пользовательских декораторов. Декораторы выполняются во время определения класса и могут, например, заменять определения полей и методов. Если им также нужно выполнить действия при создании экземпляра, они должны вызвать addInitializer(). Часто этот метод используют для добавления реактивного контроллера, чтобы декораторы могли подключаться к жизненному циклу компонента:
// A TypeScript decorator
const myDecorator = (proto: ReactiveElement, key: string) => {
const ctor = proto.constructor as typeof ReactiveElement;
ctor.addInitializer((instance: ReactiveElement) => {
// This is run during construction of the element
new MyController(instance);
});
};
// A Babel "Stage 2" decorator
const myDecorator = (descriptor) => {
...descriptor,
finisher(ctor) {
ctor.addInitializer((instance) => {
// This is run during construction of the element
new MyController(instance);
});
},
};В результате декорирование поля заставит каждый экземпляр выполнить инициализатор, добавляющий контроллер:
class MyElement extends LitElement {
@myDecorator foo;
}
Инициализаторы хранятся отдельно для каждого конструктора. Добавление инициализатора в подкласс не добавляет его в суперкласс. Поскольку инициализаторы выполняются в конструкторах, они запускаются в порядке иерархии классов — от суперклассов к классу экземпляра.
addController()
addController() добавляет реактивный контроллер в компонент Lit, чтобы компонент вызывал колбэки жизненного цикла контроллера. Дополнительную информацию см. в документации по реактивным контроллерам.
removeController()
removeController() удаляет реактивный контроллер, чтобы он больше не получал колбэки жизненного цикла от этого компонента.
Реактивный цикл обновления на стороне сервера
Пакет серверного рендеринга Lit активно разрабатывается, поэтому приведенная ниже информация может измениться.
При серверном рендеринге Lit вызываются не все методы цикла обновления. На сервере вызываются следующие методы.
© Google LLC
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://lit.dev/docs/v2/components/lifecycle/