Жизненный цикл
Глава перечисляет этапы жизни компонента, методы, которые можно переопределить, и правила освобождения ресурсов. Это нужно, чтобы компонент корректно переживал удаление и повторное добавление на страницу.
Этапы
- Тег появляется на странице; Loader подключает файлы компонента, а с первым Smart-элементом страницы — и общую среду Smart-компонентов (см. Подключение к странице).
- Тег регистрируется (тег библиотечного компонента — когда пришла общая среда); браузер создаёт экземпляр.
- Подключение: базовый класс забирает содержимое слотов из детей тега, затем
вызывает
onConnected()и отправляетsf-connected. - Отрисовка:
beforeRender()иsf-before-render, затем шаблон (внешний шаблон сначала загружается вместе со своимindex.css), затем ожидание стилей, затемafterRender()иsf-after-render. - Обновление при смене атрибутов, содержимого слотов или языка страницы:
отрисовка заново, затем
afterUpdate(),sf-updated,sf-props-change. Обновление откладывается до ближайшей микрозадачи и объединяет несколько изменений подряд: сразу послеsetAttribute()разметка ещё прежняя. Новую разметку читайте послеawait element.whenRendered(null, { next: true }). - Отключение:
onDisconnected(), освобождение внешнего шаблона,sf-disconnected.
Общие события этапов — sf-connected, sf-disconnected, sf-before-render,
sf-after-render, sf-updated и sf-props-change. Собственные события
конкретного компонента — на его странице в справочнике. События всплывают, поэтому
в составном компоненте проверяйте event.target: события вложенных элементов
тоже доходят до родителя.
Методы для переопределения
| Метод | Когда вызывается |
|---|---|
onConnected() |
После каждого подключения к странице, в том числе после повторной вставки удалённого элемента |
beforeRender(changed) |
Перед каждой отрисовкой |
afterRender(changed) |
После отрисовки и загрузки стилей |
afterUpdate(changed, mode) |
После обновления |
onDisconnected() |
При удалении со страницы |
Публичные методы для кода проекта:
| Метод | Что делает |
|---|---|
refresh(reason) |
Перерисовывает компонент, например после изменения данных, которые он читает не из атрибутов |
whenRendered(callback?, { next? }) |
Обещание, которое выполнится после отрисовки; если отрисовка уже была, выполняется сразу. Необязательная функция callback вызывается в тот же момент и получает сведения об отрисовке (component, tagName, template, changedAttributes); обещание выполняется с теми же сведениями. { next: true } ждёт следующую отрисовку, даже если первая уже была; без функции первым аргументом передают null: whenRendered(null, { next: true }) (см. Ожидание готовности) |
Правила:
-
connectedCallback()иdisconnectedCallback()целиком не переопределяют: в них базовый класс забирает слоты, подписывается на язык страницы и освобождает внешний шаблон. -
Вызов
superв этих методах не обязателен. БазовыеonConnected(),beforeRender()иafterUpdate()пусты,afterRender(changed)ничего не делает. БазовыйonDisconnected()освобождает внешний шаблон, и базовый класс делает это и сам после вашегоonDisconnected(), поэтомуsuper.onDisconnected()— безопасная привычка, а не условие. -
Поля класса и собственный конструктор допустимы: конструктор первым делом вызывает
super(), поле вродеhandleRelease = () => { … }хранит функцию экземпляра для обработчиков. Поле не называют именем объявленного свойства: пока атрибута нет,getProp()возвращает значение поля, а не значение по умолчанию. -
Базовый класс уже определяет свойство
value(чтение и запись) иstateс методомgetState(). Не перекрывайтеvalueодним геттером: присваиваниеelement.value = …станет ошибкой. Переопределяйте его паройget/setили назовите своё свойство иначе. Входvalueвstatic propsдопустим: базовыйvalueчитает и пишет атрибутvalue. -
Переопределяют только методы из таблицы выше,
template()иtemplateContext(). Остальные имена базового класса подкласс не занимает ни полем, ни методом:value(кроме парыget/set),state,style,isRendered,getState,setState,patchState,set,refresh,setHidden,isHidden,getProp,getPropValue,getPropsContext,createPropsTemplateContext,createTemplateContext,setAttributes,removeAttributes,getAttributeValue,setAttributeValue,setSlot,clearSlot,hasSlotContent,getSlotContent,whenRendered,whenChildrenDefined,requestComponentUpdate,emitComponentEvent,createRef,addRootClass,removeRootClass,toggleRootClass,setRootClass,setRootStyle,getRootClass,getRootStyle,onPropChange,onUpdate,onAfterRender. Имена свойств вродеname,size,disabled,label,min,maxиstepсвободны. -
Базовый класс не связан с формой: элемент не участвует в отправке и сбросе
<form>сам. Чтобы значение ушло с формой, шаблон рисует обычный<input name="…">(скрытый или видимый) и пишет в него значение: элемент рисует в своём DOM, без теневого корня, поэтому такое поле принадлежит форме. Сброс формы восстанавливает поле, а состояние компонента — обработчикresetформы:template() { return html`…<input type="hidden" name=${this.getProp('name')} .value=${String(this.count)}>`; }
Освобождение ресурсов
Компонент завершает свою работу при отключении: снимает обработчики на
window и document, останавливает таймеры, отменяет незавершённые запросы.
Пример показывает снятие обработчика через AbortController:
onConnected() {
this.abort = new AbortController();
window.addEventListener('resize', this.handleResize, { signal: this.abort.signal });
}
onDisconnected() {
this.abort?.abort();
super.onDisconnected();
}
handleResize — сохранённая функция экземпляра, а не новая стрелочная функция
при каждом подключении. При повторном подключении создаётся новый
AbortController: отменённый контроллер повторно не используют. Ответ на запрос,
пришедший после удаления или после нового запроса, не применяется.
Проверка: добавить элемент, выполнить действие, удалить, добавить снова. Одно действие — одно событие.
Пример готового элемента
Счётчик проходит все этапы: регистрация тега, подключение, отрисовка и обновление после нажатия «Добавить».
<project-counter count="1"></project-counter>
<p id="counter-log" role="status">Нажмите кнопку: ниже появятся данные события.</p>
async function start() {
if (typeof window.SF?.loadSmartBase !== 'function') {
await new Promise((resolve, reject) => {
const ready = () => { clearTimeout(timeout); resolve(); };
const timeout = setTimeout(() => { window.removeEventListener('sf-loader-init', ready); reject(new Error('Loader не запустился')); }, 15000);
window.addEventListener('sf-loader-init', ready, { once: true });
});
}
await SF.loadSmartBase();
const { SfBaseElement, html } = SF.smart;
// The component's own words: English in `en`, Russian in `ru`.
const TEXT = {
en: { count: 'Count', add: 'Add' },
ru: { count: 'Количество', add: 'Добавить' },
};
class ProjectCounter extends SfBaseElement {
static get props() {
// A label prop defaults to empty: the English word lives in the dictionary.
return { count: { type: Number, default: 0 }, addLabel: { default: '' } };
}
templateContext() {
// Keeps component, rootClass, rootStyle and every declared prop.
return this.createPropsTemplateContext({
countText: SF.pageText(TEXT, 'count', this),
addText: this.getProp('addLabel') || SF.pageText(TEXT, 'add', this),
});
}
increment() {
const count = this.getProp('count') + 1;
this.setAttribute('count', String(count));
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { value: count } }));
}
template() {
const { count, countText, addText, rootClass } = this.templateContext();
return html`<div class="flex gap-2 items-cross-center ${rootClass}">
<output aria-live="polite">${countText}: ${count}</output>
<button type="button"
class="sf-button sf-button--outline sf-button--on-surface sf-button--size-1/2"
@click=${() => this.increment()}>
<span class="sf-button-text-container">${addText}</span>
</button>
</div>`;
}
}
ProjectCounter.define('project-counter');
document.querySelector('project-counter').addEventListener('change', event => {
document.querySelector('#counter-log').textContent = JSON.stringify({ value: event.detail.value });
});
}
start().catch(error => { document.querySelector('#counter-log').textContent = error.message; });
Смотрите также
- События — события жизненного цикла.
- Содержимое и слоты — когда доступно содержимое слотов.
- Проверки — проверки жизненного цикла.
- Правила взаимодействия компонентов — одно действие — одна отрисовка, обновления в одном кадре.