Spec-Zone.ru › Lit 3

Встроенные директивы

Директивы — это функции, которые могут расширять Lit, настраивая способ отображения выражения. В Lit есть несколько встроенных директив для решения различных задач отображения:

Директива Краткое описание

Стилизация

classMap

Назначает элементу список классов на основе объекта.

styleMap

Задаёт элементу список свойств стиля на основе объекта.

Циклы и условия

when

Отображает один из двух шаблонов в зависимости от условия.

choose

Отображает один из нескольких шаблонов в зависимости от значения ключа.

map

Преобразует итерируемый объект с помощью функции.

repeat

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

join

Чередует значения из итерируемого объекта со значением-разделителем.

range

Создаёт итерируемую последовательность чисел; полезно для выполнения цикла заданное число раз.

ifDefined

Задаёт атрибут, если значение определено, и удаляет его, если оно не определено.

Кэширование и обнаружение изменений

cache

Кэширует отрисованный DOM при смене шаблонов, а не удаляет его.

keyed

Связывает отображаемое значение с уникальным ключом, заставляя DOM отрисовываться заново при изменении ключа.

guard

Повторно вычисляет шаблон, только если изменяется одна из его зависимостей.

live

Задаёт атрибут или свойство, если оно отличается от текущего значения в DOM, а не от значения при последней отрисовке.

Обращение к отрисованному DOM

ref

Получает ссылку на элемент, отрисованный в шаблоне.

Отображение специальных значений

templateContent

Отображает содержимое элемента <template>.

unsafeHTML

Отображает строку как HTML, а не как текст.

unsafeSVG

Отображает строку как SVG, а не как текст.

Асинхронное отображение

until

Отображает содержимое-заполнитель, пока не будет выполнен один или несколько промисов.

asyncAppend

Добавляет значения из AsyncIterable в DOM по мере их выдачи.

asyncReplace

Отображает последнее значение из AsyncIterable в DOM по мере его выдачи.

Включайте в сборку только то, что используете. Эти директивы называются «встроенными», потому что входят в пакет Lit. Однако каждая директива находится в отдельном модуле, поэтому приложение включает в сборку только импортированные директивы.

Вы также можете создавать собственные директивы. Подробнее см. в разделе Пользовательские директивы.

Стилизация

classMap

Назначает элементу список классов на основе объекта.

Импорт
import {classMap} from 'lit/directives/class-map.js';
Сигнатура
classMap(classInfo: {[name: string]: string | boolean | number})
Допустимое место использования

Выражение атрибута class (должно быть единственным выражением в атрибуте class)

Директива classMap использует API element.classList для эффективного добавления и удаления классов элемента на основе переданного пользователем объекта. Каждый ключ объекта считается именем класса; если связанное с ключом значение является истинным, этот класс добавляется элементу. При последующих отрисовках ранее заданные классы, значения которых ложны или которых больше нет в объекте, удаляются.

@customElement('my-element')
class MyElement extends LitElement {

  @property({type: Boolean})
  enabled = false;

  render() {
    const classes = { enabled: this.enabled, hidden: false };
    return html`<div class=${classMap(classes)}>Classy text</div>`;
  }
}
class MyElement extends LitElement {
  static properties = {
    enabled: {type: Boolean},
  };

  constructor() {
    super();
    this.enabled = false;
  }

  render() {
    const classes = { enabled: this.enabled, hidden: false };
    return html`<div class=${classMap(classes)}>Classy text</div>`;
  }
}
customElements.define('my-element', MyElement);

Директива classMap должна быть единственным выражением в атрибуте class, но её можно сочетать со статическими значениями:

html`<div class="my-widget ${classMap(dynamicClasses)}">Static and dynamic</div>`;

Изучите classMap подробнее в интерактивной среде.

styleMap

Задаёт элементу список свойств стиля на основе объекта.

Импорт
import {styleMap} from 'lit/directives/style-map.js';
Сигнатура
styleMap(styleInfo: {[name: string]: string | undefined | null})
Допустимое место использования

Выражение атрибута style (должно быть единственным выражением в атрибуте style)

Директива styleMap использует API element.style для эффективного добавления и удаления встроенных стилей элемента на основе переданного пользователем объекта. Каждый ключ объекта считается именем свойства стиля, а значение — значением этого свойства. При последующих отрисовках ранее заданные свойства стиля со значением undefined или null удаляются (устанавливаются в null).

@customElement('my-element')
class MyElement extends LitElement {

  @property({type: Boolean})
  enabled = false;

  render() {
    const styles = { backgroundColor: this.enabled ? 'blue' : 'gray', color: 'white' };
    return html`<p style=${styleMap(styles)}>Hello style!</p>`;
  }
}
class MyElement extends LitElement {
  static properties = {
    enabled: {type: Boolean},
  };

  constructor() {
    super();
    this.enabled = false;
  }

  render() {
    const styles = { backgroundColor: this.enabled ? 'blue' : 'gray', color: 'white' };
    return html`<p style=${styleMap(styles)}>Hello style!</p>`;
  }
}
customElements.define('my-element', MyElement);

Для CSS-свойств с дефисами можно использовать либо эквивалент в формате camelCase, либо заключить имя свойства в кавычки. Например, CSS-свойство font-family можно записать как fontFamily или 'font-family':

{ fontFamily: 'roboto' }
{ 'font-family': 'roboto' }

Чтобы обратиться к пользовательским свойствам CSS, таким как --custom-color, заключите всё имя свойства в кавычки:

{ '--custom-color': 'steelblue' }

Директива styleMap должна быть единственным выражением в атрибуте style, но её можно сочетать со статическими значениями:

html`<p style="color: white; ${styleMap(moreStyles)}">More styles!</p>`;

Изучите styleMap подробнее в интерактивной среде.

Циклы и условия

when

Отображает один из двух шаблонов в зависимости от условия.

Импорт
import {when} from 'lit/directives/when.js';
Сигнатура
when<T, F>(
  condition: boolean,
  trueCase: () => T,
  falseCase?: () => F
)
Допустимое место использования

Любое

Если condition равно true, возвращается результат вызова trueCase(); в противном случае, если falseCase определено, возвращается результат вызова falseCase().

Это удобная обёртка над тернарным выражением, которая позволяет немного проще записывать встроенное условие без ветви else.

class MyElement extends LitElement {
  render() {
    return html`
      ${when(this.user, () => html`User: ${this.user.username}`, () => html`Sign In...`)}
    `;
  }
}

choose

Выбирает и вычисляет функцию шаблона из списка вариантов, сопоставляя заданное value с одним из них.

Импорт
import {choose} from 'lit/directives/choose.js';
Сигнатура
choose<T, V>(
  value: T,
  cases: Array<[T, () => V]>,
  defaultCase?: () => V
)
Допустимое место использования

Любое

Варианты задаются как [caseValue, func]. value сопоставляется с caseValue с помощью строгого равенства. Выбирается первое совпадение. Значения вариантов могут иметь любой тип, включая примитивы, объекты и символы.

Это похоже на оператор switch, но представляет собой выражение и не допускает проваливания к следующему варианту.

class MyElement extends LitElement {
  render() {
    return html`
      ${choose(this.section, [
        ['home', () => html`<h1>Home</h1>`],
        ['about', () => html`<h1>About</h1>`]
      ],
      () => html`<h1>Error</h1>`)}
    `;
  }
}

map

Возвращает итерируемый объект, содержащий результат вызова f(value) для каждого значения в items.

Импорт
import {map} from 'lit/directives/map.js';
Сигнатура
map<T>(
  items: Iterable<T> | undefined,
  f: (value: T, index: number) => unknown
)
Допустимое место использования

Любое

map() — это простая обёртка над циклом for/of, упрощающая работу с итерируемыми объектами в выражениях. map() всегда обновляет созданные DOM-элементы на месте — без сравнения изменений и перемещения DOM. Если вам это нужно, используйте repeat. map() компактнее и быстрее, чем repeat(), поэтому, если сравнение изменений и стабильность DOM не нужны, предпочтительнее map().

class MyElement extends LitElement {
  render() {
    return html`
      <ul>
        ${map(items, (i) => html`<li>${i}</li>`)}
      </ul>
    `;
  }
}

repeat

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

Импорт
import {repeat} from 'lit/directives/repeat.js';
Сигнатура
repeat(items: Iterable<T>, keyfn: KeyFn<T>, template: ItemTemplate<T>)
repeat(items: Iterable<T>, template: ItemTemplate<T>)
type KeyFn<T> = (item: T, index: number) => unknown;
type ItemTemplate<T> = (item: T, index: number) => unknown;
Допустимое место использования

Дочернее выражение

Повторяет последовательность значений (обычно TemplateResults), созданных на основе итерируемого объекта, и эффективно обновляет элементы при изменении итерируемого объекта. Если задан keyFn, при обновлениях сохраняется связь ключей с DOM: при необходимости созданные DOM-элементы перемещаются. Обычно это самый эффективный способ использования repeat, поскольку он сводит к минимуму ненужную работу при вставке и удалении элементов.

Если вы не используете функцию ключа, рассмотрите возможность применения map().

@customElement('my-element')
class MyElement extends LitElement {

  @property()
  items: Array<{id: number, name: string}> = [];

  render() {
    return html`
      <ul>
        ${repeat(this.items, (item) => item.id, (item, index) => html`
          <li>${index}: ${item.name}</li>`)}
      </ul>
    `;
  }
}
class MyElement extends LitElement {
  static properties = {
    items: {},
  };

  constructor() {
    super();
    this.items = [];
  }

  render() {
    return html`
      <ul>
        ${repeat(this.items, (item) => item.id, (item, index) => html`
          <li>${index}: ${item.name}</li>`)}
      </ul>
    `;
  }
}
customElements.define('my-element', MyElement);

Если keyFn не задан, repeat работает примерно как простое сопоставление элементов со значениями, а DOM повторно используется для потенциально других элементов.

В разделе Когда использовать map или repeat обсуждается, когда применять repeat, а когда — стандартные средства управления потоком JavaScript.

Изучите repeat подробнее в интерактивной среде.

join

Возвращает итерируемый объект, в котором значения items чередуются со значением joiner.

Импорт
import {join} from 'lit/directives/join.js';
Сигнатура
join<I, J>(
  items: Iterable<I> | undefined,
  joiner: J
): Iterable<I | J>;

join<I, J>(
  items: Iterable<I> | undefined,
  joiner: (index: number) => J
): Iterable<I | J>;
Допустимое место использования

Любое

class MyElement extends LitElement {

  render() {
    return html`
      ${join(
        map(menuItems, (i) => html`<a href=${i.href}>${i.label}</a>`),
        html`<span class="separator">|</span>`
      )}
    `;
  }
}

range

Возвращает итерируемую последовательность целых чисел от start до end (не включая последнее), увеличивая значение на step.

Импорт
import {range} from 'lit/directives/range.js';
Сигнатура
range(end: number): Iterable<number>;

range(
  start: number,
  end: number,
  step?: number
): Iterable<number>;
Допустимое место использования

Любое

class MyElement extends LitElement {

  render() {
    return html`
      ${map(range(8), (i) => html`${i + 1}`)}
    `;
  }
}

ifDefined

Задаёт атрибут, если значение определено, и удаляет его, если оно не определено.

Импорт
import {ifDefined} from 'lit/directives/if-defined.js';
Сигнатура
ifDefined(value: unknown)
Допустимое место использования

Выражение атрибута

Для AttributeParts атрибут задаётся, если значение определено, и удаляется, если значение не определено (undefined или null). Для других типов частей эта директива ничего не делает.

Если в одном значении атрибута содержится несколько выражений, атрибут будет удалён, если любое из выражений использует ifDefined и вычисляется в undefined/null. Это особенно полезно при задании URL-атрибутов: атрибут не должен задаваться, если обязательные части URL не определены, чтобы избежать ошибок 404.

@customElement('my-element')
class MyElement extends LitElement {

  @property()
  filename: string | undefined = undefined;

  @property()
  size: string | undefined = undefined;

  render() {
    // src attribute not rendered if either size or filename are undefined
    return html`<img src="/images/${ifDefined(this.size)}/${ifDefined(this.filename)}">`;
  }
}
class MyElement extends LitElement {
  static properties = {
    filename: {},
    size: {},
  };

  constructor() {
    super();
    this.filename = undefined;
    this.size = undefined;
  }

  render() {
    // src attribute not rendered if either size or filename are undefined
    return html`<img src="/images/${ifDefined(this.size)}/${ifDefined(this.filename)}">`;
  }
}
customElements.define('my-element', MyEleent);

Изучите ifDefined подробнее в интерактивной среде.

Кэширование и обнаружение изменений

cache

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

Импорт
import {cache} from 'lit/directives/cache.js';
Сигнатура
cache(value: TemplateResult|unknown)
Допустимое место использования

Дочернее выражение

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

const detailView = (data) => html`<div>...</div>`;
const summaryView = (data) => html`<div>...</div>`;

@customElement('my-element')
class MyElement extends LitElement {

  @property()
  data = {showDetails: true, /*...*/ };

  render() {
    return html`${cache(this.data.showDetails
      ? detailView(this.data)
      : summaryView(this.data)
    )}`;
  }
}
const detailView = (data) => html`<div>...</div>`;
const summaryView = (data) => html`<div>...</div>`;

class MyElement extends LitElement {
  static properties = {
    data: {},
  };

  constructor() {
    super();
    this.data = {showDetails: true, /*...*/ };
  }

  render() {
    return html`${cache(this.data.showDetails
      ? detailView(this.data)
      : summaryView(this.data)
    )}`;
  }
}
customElements.define('my-element', MyElement);

При повторной отрисовке шаблона Lit обновляет только изменённые части: он не создаёт и не удаляет DOM-элементы сверх необходимого. Однако при переключении с одного шаблона на другой Lit удаляет старый DOM и отображает новое дерево DOM.

Директива cache кэширует созданный DOM для заданного выражения и входного шаблона. В примере выше она кэширует DOM для обоих шаблонов: summaryView и detailView. При переключении с одного представления на другое Lit подставляет кэшированную версию нового представления и обновляет её последними данными. Это может повысить производительность отрисовки, если представления часто переключаются.

Изучите cache подробнее в интерактивной среде.

keyed

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

Импорт
import {keyed} from 'lit/directives/keyed.js';
Сигнатура
keyed(key: unknown, value: unknown)
Допустимое место использования

Любое выражение

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

keyed также полезна в некоторых сценариях анимации, когда для анимации «появления» или «исчезновения» требуется принудительно создать новый элемент.

@customElement('my-element')
class MyElement extends LitElement {

  @property()
  userId: string = '';

  render() {
    return html`
      <div>
        ${keyed(this.userId, html`<user-card .userId=${this.userId}></user-card>`)}
      </div>`;
  }
}
class MyElement extends LitElement {
  static properties = {
    userId: {},
  };

  constructor() {
    super();
    this.userId = '';
  }

  render() {
    return html`
      <div>
        ${keyed(this.userId, html`<user-card .userId=${this.userId}></user-card>`)}
      </div>`;
  }
}
customElements.define('my-element', MyElement);

guard

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

Импорт
import {guard} from 'lit/directives/guard.js';
Сигнатура
guard(dependencies: unknown[], valueFn: () => unknown)
Допустимое место использования

Любое выражение

Отображает значение, возвращённое valueFn, и повторно вычисляет valueFn, только если изменяется идентичность одной из зависимостей.

Здесь:

  • dependencies — массив значений, отслеживаемых на предмет изменений.
  • valueFn — функция, возвращающая отображаемое значение.

guard полезна при использовании шаблонов с неизменяемыми данными: она предотвращает дорогостоящие вычисления до обновления данных.

@customElement('my-element')
class MyElement extends LitElement {

  @property()
  value: string = '';

  render() {
    return html`
      <div>
        ${guard([this.value], () => calculateSHA(this.value))}
      </div>`;
  }
}
class MyElement extends LitElement {
  static properties = {
    value: {},
  };

  constructor() {
    super();
    this.value = '';
  }

  render() {
    return html`
      <div>
        ${guard([this.value], () => calculateSHA(this.value))}
      </div>`;
  }
}
customElements.define('my-element', MyElement);

В этом случае ресурсоёмкая функция calculateSHA запускается только при изменении свойства value.

Изучите guard подробнее в интерактивной среде.

live

Задаёт атрибут или свойство, если оно отличается от текущего значения в DOM, а не от значения при последней отрисовке.

Импорт
import {live} from 'lit/directives/live.js';
Сигнатура
live(value: unknown)
Допустимое место использования

Выражение атрибута или свойства

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

Это полезно, если значение DOM может измениться за пределами Lit. Например, при использовании выражения для задания свойства value элемента <input>, текста редактируемого содержимого или свойств либо атрибутов пользовательского элемента, который изменяет их самостоятельно.

В таких случаях, если значение DOM изменится, а значение, заданное через выражение Lit, останется прежним, Lit не узнает, что DOM нужно обновить, и оставит значение без изменений. Если это не соответствует вашим требованиям — если нужно перезаписывать значение DOM связанным значением при любых обстоятельствах, — используйте директиву live().

@customElement('my-element')
class MyElement extends LitElement {

  @property()
  data = {value: 'test'};

  render() {
    return html`<input .value=${live(this.data.value)}>`;
  }
}
class MyElement extends LitElement {
  static properties = {
    data: {},
  };

  constructor() {
    super();
    this.data = {value: 'test'};
  }

  render() {
    return html`<input .value=${live(this.data.value)}>`;
  }
}
customElements.define('my-element', MyElement);

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

Изучите live подробнее в интерактивной среде.

Отображение специальных значений

templateContent

Отображает содержимое элемента <template>.

Импорт
import {templateContent} from 'lit/directives/template-content.js';
Сигнатура
templateContent(templateElement: HTMLTemplateElement)
Допустимое место использования

Дочернее выражение

Шаблоны Lit записываются на JavaScript, что позволяет включать в них динамические выражения JavaScript. Если в шаблон Lit нужно включить статический HTML <template>, воспользуйтесь директивой templateContent: она клонирует содержимое шаблона и включает его в шаблон Lit. Если ссылка на элемент template не меняется между отрисовками, при последующих отрисовках ничего не происходит.

Обратите внимание: содержимое шаблона должно контролироваться разработчиком и не должно создаваться из недоверенной строки. К недоверенному содержимому относятся, например, параметры строки запроса и значения, введённые пользователями. Отображение недоверенных шаблонов с помощью этой директивы может привести к уязвимостям, связанным с межсайтовым скриптингом (XSS).

const templateEl = document.querySelector('template#myContent') as HTMLTemplateElement;

@customElement('my-element')
class MyElement extends LitElement {

  render() {
    return  html`
      Here's some content from a template element:
      ${templateContent(templateEl)}`;
  }
}
const templateEl = document.querySelector('template#myContent');

class MyElement extends LitElement {

  render() {
    return  html`
      Here's some content from a template element:
      ${templateContent(templateEl)}`;
  }
}
customElements.define('my-element', MyElement);

Изучите templateContent подробнее в интерактивной среде.

unsafeHTML

Отображает строку как HTML, а не как текст.

Импорт
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
Сигнатура
unsafeHTML(value: string | typeof nothing | typeof noChange)
Допустимое место использования

Дочернее выражение

Важная особенность синтаксиса шаблонов Lit заключается в том, что как HTML разбираются только строки, заданные в шаблонных литералах. Поскольку шаблонные литералы можно создавать только в доверенных файлах скриптов, это служит естественной защитой от XSS-атак, внедряющих недоверенный HTML. Однако иногда в шаблон Lit нужно включить HTML, полученный не из файла скрипта, например доверенное HTML-содержимое из базы данных. Директива unsafeHTML разбирает такую строку как HTML и отображает её в шаблоне Lit.

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

Отображение недоверенного содержимого с помощью этой директивы может привести к уязвимостям, связанным с межсайтовым скриптингом (XSS), внедрением CSS, утечкой данных и т. д. Директива unsafeHTML использует innerHTML для разбора HTML-строки, поэтому риски безопасности такие же, как и у innerHTML, описанного на MDN.

const markup = '<h3>Some HTML to render.</h3>';

@customElement('my-element')
class MyElement extends LitElement {

  render() {
    return html`
      Look out, potentially unsafe HTML ahead:
      ${unsafeHTML(markup)}
    `;
  }
}
const markup = '<h3>Some HTML to render.</h3>';

class MyElement extends LitElement {

  render() {
    return html`
      Look out, potentially unsafe HTML ahead:
      ${unsafeHTML(markup)}
    `;
  }
}
customElements.define('my-element', MyElement);

Изучите unsafeHTML подробнее в интерактивной среде.

unsafeSVG

Отображает строку как SVG, а не как текст.

Импорт
import {unsafeSVG} from 'lit/directives/unsafe-svg.js';
Сигнатура
unsafeSVG(value: string | typeof nothing | typeof noChange)
Допустимое место использования

Дочернее выражение

Как и в случае с unsafeHTML, иногда в шаблон Lit нужно включить содержимое SVG, полученное не из файла скрипта, например доверенное содержимое SVG из базы данных. Директива unsafeSVG разбирает такую строку как SVG и отображает её в шаблоне Lit.

Обратите внимание: строка, передаваемая в unsafeSVG, должна контролироваться разработчиком и не должна содержать недоверенное содержимое. К недоверенному содержимому относятся, например, параметры строки запроса и значения, введённые пользователями. Отображение недоверенного содержимого с помощью этой директивы может привести к уязвимостям, связанным с межсайтовым скриптингом (XSS).

const svg = '<circle cx="50" cy="50" r="40" fill="red" />';

@customElement('my-element')
class MyElement extends LitElement {

  render() {
    return html`
      Look out, potentially unsafe SVG ahead:
      <svg width="40" height="40" viewBox="0 0 100 100"
        xmlns="http://www.w3.org/2000/svg" version="1.1">
        ${unsafeSVG(svg)}
      </svg> `;
  }
}
const svg = '<circle cx="50" cy="50" r="40" fill="red" />';

class MyElement extends LitElement {

  render() {
    return html`
      Look out, potentially unsafe SVG ahead:
      <svg width="40" height="40" viewBox="0 0 100 100"
        xmlns="http://www.w3.org/2000/svg" version="1.1">
        ${unsafeSVG(svg)}
      </svg> `;
  }
}
customElements.define('my-element', MyElement);

Изучите unsafeSVG подробнее в интерактивной среде.

Обращение к отрисованному DOM

ref

Получает ссылку на элемент, отрисованный в DOM.

Импорт
import {ref} from 'lit/directives/ref.js';
Сигнатура
ref(refOrCallback: RefOrCallback)
Допустимое место использования

Выражение элемента

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

Если директива ref размещена на элементе шаблона, она получает ссылку на этот элемент после его отрисовки. Ссылку на элемент можно получить одним из двух способов: передать объект Ref или передать функцию обратного вызова.

Объект Ref служит контейнером для ссылки на элемент; его можно создать с помощью вспомогательного метода createRef из модуля ref. После отрисовки свойству value объекта Ref будет присвоен элемент. К нему можно обратиться в хуках жизненного цикла после отрисовки, например в updated.

@customElement('my-element')
class MyElement extends LitElement {

  inputRef: Ref<HTMLInputElement> = createRef();

  render() {
    // Passing ref directive a Ref object that will hold the element in .value
    return html`<input ${ref(this.inputRef)}>`;
  }

  firstUpdated() {
    const input = this.inputRef.value!;
    input.focus();
  }
}
class MyElement extends LitElement {

  inputRef = createRef();

  render() {
    // Passing ref directive a Ref object that will hold the element in .value
    return html`<input ${ref(this.inputRef)}>`;
  }

  firstUpdated() {
    const input = this.inputRef.value!;
    input.focus();
  }
}
customElements.define('my-element', MyElement);

Директиве ref также можно передать функцию обратного вызова ref. Эта функция будет вызываться каждый раз, когда меняется связанный элемент. Если при последующей отрисовке функция обратного вызова ref будет привязана к другой позиции элемента или удалена, сначала она будет вызвана с аргументом undefined, а затем ещё раз — с новым элементом, к которому она привязана (если он есть). Обратите внимание: внутри LitElement функция обратного вызова автоматически вызывается с привязкой к элементу-хозяину.

@customElement('my-element')
class MyElement extends LitElement {

  render() {
    // Passing ref directive a change callback
    return html`<input ${ref(this.inputChanged)}>`;
  }

  inputChanged(input?: HTMLInputElement) {
    input?.focus();
  }
}
class MyElement extends LitElement {

  render() {
    // Passing ref directive a change callback
    return html`<input ${ref(this.inputChanged)}>`;
  }

  inputChanged(input) {
    input?.focus();
  }
}
customElements.define('my-element', MyElement);

Изучите ref подробнее в интерактивной среде.

Асинхронное отображение

until

Отображает содержимое-заполнитель, пока не будет выполнен один или несколько промисов.

Импорт
import {until} from 'lit/directives/until.js';
Сигнатура
until(...values: unknown[])
Допустимое место использования

Любое выражение

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

Приоритет значений можно использовать для создания содержимого-заполнителя для асинхронных данных. Например, промис с ожидаемым результатом может быть первым аргументом (с наивысшим приоритетом), а шаблон индикатора загрузки без промиса — вторым аргументом (с более низким приоритетом). Индикатор загрузки отображается сразу, а основное содержимое появится после выполнения промиса.

@customElement('my-element')
class MyElement extends LitElement {

  @state()
  private content = fetch('./content.txt').then(r => r.text());

  render() {
    return html`${until(this.content, html`<span>Loading...</span>`)}`;
  }
}
class MyElement extends LitElement {
  static properties = {
    content: {state: true},
  };

  constructor() {
    super();
    this.content = fetch('./content.txt').then(r => r.text());
  }

  render() {
    return html`${until(this.content, html`<span>Loading...</span>`)}`;
  }
}
customElements.define('my-element', MyElement);

Узнайте больше о until в песочнице.

asyncAppend

Добавляет значения из AsyncIterable в DOM по мере их получения.

Импорт
import {asyncAppend} from 'lit/directives/async-append.js';
Сигнатура
asyncAppend(
  iterable: AsyncIterable<I>,
  mapper?: (item: I, index?: number) => unknown
)
Допустимое место использования

Выражение дочернего элемента

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

async function *tossCoins(count: number) {
  for (let i=0; i<count; i++) {
    yield Math.random() > 0.5 ? 'Heads' : 'Tails';
    await new Promise((r) => setTimeout(r, 1000));
  }
}

@customElement('my-element')
class MyElement extends LitElement {

  @state()
  private tosses = tossCoins(10);

  render() {
    return html`
      <ul>${asyncAppend(this.tosses, (v: string) => html`<li>${v}</li>`)}</ul>`;
  }
}
async function *tossCoins(count) {
  for (let i=0; i<count; i++) {
    yield Math.random() > 0.5 ? 'Heads' : 'Tails';
    await new Promise((r) => setTimeout(r, 1000));
  }
}

class MyElement extends LitElement {
  static properties = {
    tosses: {state: true},
  };

  constructor() {
    super();
    this.tosses = tossCoins(10);
  }

  render() {
    return html`
      <ul>${asyncAppend(this.tosses, (v) => html`<li>${v}</li>`)}</ul>`;
  }
}
customElements.define('my-element', MyElement);

Узнайте больше о asyncAppend в песочнице.

asyncReplace

Отображает последнее значение из AsyncIterable в DOM по мере его получения.

Импорт
import {asyncReplace} from 'lit/directives/async-replace.js';
Сигнатура
asyncReplace(
  iterable: AsyncIterable<I>,
  mapper?: (item: I, index?: number) => unknown
)
Допустимое место использования

Любое выражение

Как и asyncAppend, asyncReplace отображает значения асинхронного итерируемого объекта, заменяя предыдущее значение каждым новым.

async function *countDown(count: number) {
  while (count > 0) {
    yield count--;
    await new Promise((r) => setTimeout(r, 1000));
  }
}

@customElement('my-element')
class MyElement extends LitElement {

  @state()
  private timer = countDown(10);

  render() {
    return html`Timer: <span>${asyncReplace(this.timer)}</span>.`;
  }
}
async function *countDown(count) {
  while (count > 0) {
    yield count--;
    await new Promise((r) => setTimeout(r, 1000));
  }
}

class MyElement extends LitElement {
  static properties = {
    timer: {state: true},
  };

  constructor() {
    super();
    this.timer = countDown(10);
  }

  render() {
    return html`Timer: <span>${asyncReplace(this.timer)}</span>.`;
  }
}
customElements.define('my-element', MyElement);

Узнайте больше о asyncReplace в песочнице.

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

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

Spec-Zone.ru

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