Spec-Zone.ru › Lit 1

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

Обзор

Компоненты на основе LitElement обновляются асинхронно в ответ на изменения наблюдаемых свойств. Изменения свойств объединяются в пакет: если после запроса обновления, но до его начала, изменятся другие свойства, все изменения будут учтены в одном обновлении.

В общих чертах жизненный цикл обновления выглядит так:

  1. Устанавливается свойство.
  2. Проверяется, требуется ли обновление. Если требуется, оно запрашивается.
  3. Выполняется обновление:
  • Обрабатываются свойства и атрибуты.
  • Выполняется рендеринг элемента.
  1. Разрешается Promise, указывающий на завершение обновления.

LitElement и цикл событий браузера

Браузер выполняет код JavaScript, обрабатывая очередь задач в цикле событий. На каждой итерации цикла событий браузер берёт задачу из очереди и выполняет её до завершения.

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

По умолчанию обновления LitElement запрашиваются асинхронно и ставятся в очередь как микрозадачи. Это означает, что шаг 3 выше (выполнение обновления) выполняется в конце следующей итерации цикла событий.

Можно изменить это поведение так, чтобы перед выполнением обновления на шаге 3 ожидался Promise. Подробнее см. в разделе performUpdate.

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

Обратные вызовы жизненного цикла

LitElement также наследует стандартные обратные вызовы жизненного цикла из стандарта веб-компонентов:

  • connectedCallback: вызывается, когда компонент добавляется в DOM документа.
  • disconnectedCallback: вызывается, когда компонент удаляется из DOM документа.
  • adoptedCallback: вызывается, когда компонент перемещается в новый документ.
  • attributeChangedCallback: вызывается при изменении атрибута компонента.

Обратите внимание: для adoptedCallback полифил не предоставляется.

Во всех методах жизненного цикла необходимо вызывать метод super.

Пример:

connectedCallback() {
  super.connectedCallback()

  console.log('connected')
}

Promise и асинхронные функции

LitElement использует объекты Promise для планирования обновлений элементов и реагирования на них.

С помощью async и await удобно работать с Promise. Например, можно дождаться Promise updateComplete:

// `async` makes the function return a Promise & lets you use `await`
async myFunc(data) {
  // Set a property, triggering an update
  this.myProp = data;

  // Wait for the updateComplete promise to resolve
  await this.updateComplete;
  // ...do stuff...
  return 'done';
}

Поскольку функции async возвращают Promise, их также можно ожидать:

let result = await myFunc('stuff');
// `result` is resolved! You can do something with it

Более подробное руководство см. во вводном руководстве Web Fundamentals по Promise.

Различные сценарии использования

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

  • Используйте property.hasChanged, чтобы проверить: «Это изменение? Нужно ли запускать цикл обновления?».
  • Используйте конструктор элемента для инициализации свойств LitElement со значениями по умолчанию. (Значения атрибутов из DOM недоступны во время выполнения конструктора.)
  • Используйте firstUpdated для инициализации приватных полей значениями атрибутов DOM (поскольку конструктор не имеет к ним доступа). Обратите внимание: к этому моменту render уже был вызван, и ваши изменения могут запустить ещё один цикл обновления. Если вам обязательно нужен доступ к значениям атрибутов до первого рендеринга, рассмотрите возможность использования connectedCallback, но дополнительную логику для определения «первого» обновления придётся реализовать самостоятельно, так как connectedCallback может вызываться несколько раз.
  • Используйте updated, чтобы поддерживать актуальность производных данных или реагировать на изменения. Если это приводит к повторному рендерингу, рассмотрите возможность использования update.
  • Используйте пользовательские геттеры свойств JS для производных данных, которые несложно вычислить, если они вряд ли будут часто меняться и ваш элемент нечасто выполняет повторный рендеринг.
  • Используйте requestUpdate, чтобы запустить цикл обновления, если LitElement не может обнаружить изменение самостоятельно. (Например, если у вас есть наблюдаемое свойство — массив, и вы добавляете в этот массив элемент, а не заменяете весь массив, LitElement не «увидит» это изменение, потому что ссылка на массив не изменилась.)
  • Используйте connectedCallback, чтобы регистрировать обработчики событий для событий вне шаблона элемента, но не забудьте удалить их в disconnectedCallback!

Справочник по методам и свойствам

Методы и свойства цикла обновления в порядке вызова:

  1. someProperty.hasChanged
  2. requestUpdate
  3. performUpdate
  4. shouldUpdate
  5. update
  6. render
  7. firstUpdated
  8. updated
  9. updateComplete

someProperty.hasChanged

У всех объявленных свойств есть функция hasChanged, которая вызывается при каждой установке свойства; если hasChanged возвращает true, планируется обновление.

Информацию о том, как настроить hasChanged и определить, что считать изменением свойства, см. в документации по свойствам.

requestUpdate

// Manually start an update
this.requestUpdate();

// Call from within a custom property setter
this.requestUpdate(propertyName, oldValue);
Параметры

propertyName

oldValue
Имя свойства, которое нужно обновить.

Предыдущее значение свойства.
Возвращает Promise Возвращает Promise updateComplete, который разрешается по завершении обновления.
Запускает обновление? Нет Изменения свойств внутри этого метода не запускают обновление элемента.

Если hasChanged вернул true, вызывается requestUpdate, и обновление продолжается.

Чтобы вручную запустить обновление элемента, вызовите requestUpdate без параметров.

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

Пример: вручную запустить обновление элемента

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  constructor() {
    super();

    // Request an update in response to an event
    this.addEventListener('load-complete', async (e) => {
      console.log(e.detail.message);
      console.log(await this.requestUpdate());
    });
  }
  render() {
    return html`
      <button @click="${this.fire}">Fire a "load-complete" event</button>
    `;
  }
  fire() {
    let newMessage = new CustomEvent('load-complete', {
      detail: { message: 'hello. a load-complete happened.' }
    });
    this.dispatchEvent(newMessage);
  }
}
customElements.define('my-element', MyElement);

Редактор кода не поддерживается в этом браузере

Пример: вызвать requestUpdate из пользовательского метода установки свойства

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() {
    return { prop: { type: Number } };
  }

  set prop(val) {
    let oldVal = this._prop;
    this._prop = Math.floor(val);
    this.requestUpdate('prop', oldVal);
  }

  get prop() { return this._prop; }

  constructor() {
    super();
    this._prop = 0;
  }

  render() {
    return html`
      <p>prop: ${this.prop}</p>
      <button @click="${() =>  { this.prop = Math.random()*10; }}">
        change prop
      </button>
    `;
  }
}
customElements.define('my-element', MyElement);

Редактор кода не поддерживается в этом браузере

performUpdate

/**
 * Implement to override default behavior.
 */
performUpdate() { ... }
Возвращает void или Promise Выполняет обновление.
Запускает обновление? Нет Изменения свойств внутри этого метода не запускают обновление элемента.

При выполнении обновления вызывается метод performUpdate(). Этот метод вызывает ряд других методов жизненного цикла.

Изменения, которые обычно запускают обновление и происходят во время обновления компонента, не планируют новое обновление. Это сделано для того, чтобы значения свойств можно было вычислять в процессе обновления. Свойства, изменённые во время обновления, отражаются в карте changedProperties, поэтому последующие методы жизненного цикла могут учитывать эти изменения.

По умолчанию performUpdate планируется как микрозадача после завершения следующего выполнения цикла событий браузера. Чтобы запланировать performUpdate, реализуйте его как асинхронный метод, который ожидает определённого состояния, а затем вызывает super.performUpdate(). Например:

async performUpdate() {
  await new Promise((resolve) => requestAnimationFrame(() => resolve()));
  super.performUpdate();
}

Редактор кода не поддерживается в этом браузере

shouldUpdate

/**
 * Implement to override default behavior.
 */
shouldUpdate(changedProperties) { ... }
Параметры changedProperties Map. Ключи — имена изменившихся свойств; значения — соответствующие предыдущие значения.
Возвращает Boolean Если true, обновление продолжается. Значение, возвращаемое по умолчанию, — true.
Запускает обновление? Нет Изменения свойств внутри этого метода не запускают обновление элемента.

Определяет, должно ли выполняться обновление. Реализуйте shouldUpdate, чтобы указать, какие изменения свойств должны вызывать обновления. По умолчанию этот метод всегда возвращает true.

Пример: настроить, какие изменения свойств должны вызывать обновления

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() {
    return {
      prop1: { type: Number },
      prop2: { type: Number }
    };
  }
  constructor() {
    super();
    this.prop1 = 0;
    this.prop2 = 0;
  }

  render() {
    return html`
      <p>prop1: ${this.prop1}</p>
      <p>prop2: ${this.prop2}</p>
      <button @click="${() => this.prop1=this.change()}">Change prop1</button>
      <button @click="${() => this.prop2=this.change()}">Change prop2</button>
    `;
  }

  /**
   * Only update element if prop1 changed.
   */
  shouldUpdate(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
      console.log(`${propName} changed. oldValue: ${oldValue}`);
    });
    return changedProperties.has('prop1');
  }

  change() {
    return Math.floor(Math.random()*10);
  }
}
customElements.define('my-element', MyElement);

Редактор кода не поддерживается в этом браузере

update

Параметры changedProperties Map. Ключи — имена изменившихся свойств; значения — соответствующие предыдущие значения.
Запускает обновление? Нет Изменения свойств внутри этого метода не запускают обновление элемента.

Отражает значения свойств в атрибутах и вызывает render для рендеринга DOM с помощью lit-html. Этот метод приведён для справки. Переопределять или вызывать его не нужно. Но если вы его переопределите, обязательно вызовите super.update(changedProperties), иначе render никогда не будет вызван.

render

/**
 * Implement to override default behavior.
 */
render() { ... }
Возвращает TemplateResult Должен возвращать TemplateResult lit-html.
Запускает обновление? Нет Изменения свойств внутри этого метода не запускают обновление элемента.

Использует lit-html для рендеринга шаблона элемента. Необходимо реализовать render для любого компонента, расширяющего базовый класс LitElement.

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

firstUpdated

/**
 * Implement to override default behavior.
 */
firstUpdated(changedProperties) { ... }
Параметры changedProperties Map. Ключи — имена изменившихся свойств; значения — соответствующие предыдущие значения.
Запускает обновление? Да Изменения свойств внутри этого метода запускают обновление элемента.

Вызывается после первого обновления DOM элемента, непосредственно перед вызовом updated.

Реализуйте firstUpdated, чтобы выполнить одноразовые действия после создания шаблона элемента.

Пример: установить фокус на элементе ввода при первом обновлении

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() {
    return {
      textAreaId: { type: String },
      startingText: { type: String }
    };
  }
  constructor() {
    super();
    this.textAreaId = 'myText';
    this.startingText = 'Focus me on first update';
  }
  render() {
    return html`
      <textarea id="${this.textAreaId}">${this.startingText}</textarea>
    `;
  }
  firstUpdated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
      console.log(`${propName} changed. oldValue: ${oldValue}`);
    });
    const textArea = this.shadowRoot.getElementById(this.textAreaId);
    textArea.focus();
  }
}
customElements.define('my-element', MyElement);

Редактор кода не поддерживается в этом браузере

updated

/**
 * Implement to override default behavior.
 */
updated(changedProperties) { ... }
Параметры changedProperties Map. Ключи — имена изменившихся свойств; значения — соответствующие предыдущие значения.
Запускает обновление? Да Изменения свойств внутри этого метода запускают обновление элемента.

Вызывается после обновления и рендеринга DOM элемента. Реализуйте этот метод, чтобы выполнять задачи после обновления.

Пример: установить фокус на элементе после обновления

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() {
    return {
      prop1: { type: Number },
      prop2: { type: Number }
    };
  }
  constructor() {
    super();
    this.prop1 = 0;
    this.prop2 = 0;
  }
  render() {
    return html`
      <style>button:focus { background-color: aliceblue; }</style>

      <p>prop1: ${this.prop1}</p>
      <p>prop2: ${this.prop2}</p>

      <button id="a" @click="${() => this.prop1=Math.random()}">prop1</button>
      <button id="b" @click="${() => this.prop2=Math.random()}">prop2</button>
    `;
  }
  updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
      console.log(`${propName} changed. oldValue: ${oldValue}`);
    });
    let b = this.shadowRoot.getElementById('b');
    b.focus();
  }
}
customElements.define('my-element', MyElement);

Редактор кода не поддерживается в этом браузере

updateComplete

// Await Promise property.
await this.updateComplete;
Тип Promise Разрешается со значением Boolean, когда обновление элемента завершено.
Разрешается

true, если ожидающих обновлений больше нет.

false, если этот цикл обновления запустил ещё одно обновление.

Promise updateComplete разрешается, когда обновление элемента завершено. Используйте updateComplete, чтобы дождаться обновления:

await this.updateComplete;
// do stuff
this.updateComplete.then(() => { /* do stuff */ });

Пример

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() {
    return {
      prop1: { type: Number }
    };
  }

  constructor() {
    super();
    this.prop1 = 0;
  }

  render() {
    return html`
      <p>prop1: ${this.prop1}</p>
      <button @click="${this.changeProp}">prop1</button>
    `;
  }

  async getMoreState() {
    return;
  }

  async changeProp() {
    this.prop1 = Math.random();
    await Promise.all([this.updateComplete, this.getMoreState()]);
    console.log('Update complete. Other state completed.');
  }
}

customElements.define('my-element', MyElement);

Редактор кода не поддерживается в этом браузере

Переопределение updateComplete

Чтобы дождаться дополнительного состояния перед разрешением Promise updateComplete, переопределите метод _getUpdateComplete. Например, здесь может быть полезно дождаться обновления дочернего элемента. Сначала дождитесь super._getUpdateComplete(), а затем — любого последующего состояния.

Рекомендуется переопределять метод _getUpdateComplete, а не геттер updateComplete, чтобы обеспечить совместимость с пользователями, использующими вывод TypeScript ES5 (см. TypeScript#338).

class MyElement extends LitElement {
  async _getUpdateComplete() {
    await super._getUpdateComplete();
    await this._myChild.updateComplete;
  }
}

Примеры

Управление временем выполнения обновлений

Реализуйте performUpdate:

async performUpdate() {
  await new Promise((resolve) => requestAnimationFrame(() => resolve());
  super.performUpdate();
}

Редактор кода не поддерживается в этом браузере

Настройка изменений свойств, вызывающих обновление

Реализуйте shouldUpdate:

shouldUpdate(changedProps) {
  return changedProps.has('prop1');
}

Редактор кода не поддерживается в этом браузере

Настройка определения изменения свойства

Укажите hasChanged для свойства. См. документацию по свойствам.

Управление изменениями свойств и обновлениями для вложенных свойств объектов

Мутации (изменения вложенных свойств объектов и элементов массивов) не отслеживаются. Вместо этого полностью замените объект или вызовите requestUpdate после мутации.

// Option 1: Rewrite whole object, triggering an update
this.prop1 = Object.assign({}, this.prop1, { subProp: 'data' });

// Option 2: Mutate a subproperty, then call requestUpdate
this.prop1.subProp = 'data';
this.requestUpdate();

Редактор кода не поддерживается в этом браузере

Обновление в ответ на событие, не связанное с изменением свойства

Вызовите requestUpdate:

// Request an update in response to an event
this.addEventListener('load-complete', async (e) => {
  console.log(e.detail.message);
  console.log(await this.requestUpdate());
});

Редактор кода не поддерживается в этом браузере

Запрос обновления независимо от изменений свойств

Вызовите requestUpdate():

this.requestUpdate();

Запрос обновления для определённого свойства

Вызовите requestUpdate(propName, oldValue):

let oldValue = this.prop1;
this.prop1 = 'new value';
this.requestUpdate('prop1', oldValue);

Редактор кода не поддерживается в этом браузере

Выполнение действий после первого обновления

Реализуйте firstUpdated:

firstUpdated(changedProps) {
  console.log(changedProps.get('prop1'));
}

Редактор кода не поддерживается в этом браузере

Выполнение действий после каждого обновления

Реализуйте updated:

updated(changedProps) {
  console.log(changedProps.get('prop1'));
}

Редактор кода не поддерживается в этом браузере

Выполнение действий при следующем обновлении элемента

Дождитесь Promise updateComplete:

await this.updateComplete;
// do stuff
this.updateComplete.then(() => {
  // do stuff
});

Ожидание завершения обновления элемента

Дождитесь Promise updateComplete:

let done = await updateComplete;
updateComplete.then(() => {
  // finished updating
});

Редактор кода не поддерживается в этом браузере

Редактировать эту страницу

© Google LLC
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://lit.dev/docs/v1/components/lifecycle/

Spec-Zone.ru

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