Spec-Zone.ru › Lit 1

Написание шаблонов

lit-html — это библиотека шаблонов, которая обеспечивает быстрое и эффективное отображение HTML и обновление его содержимого. Она позволяет описывать веб-интерфейс как функцию от данных.

В этом разделе представлены основные возможности и понятия lit-html.

Отображение статического HTML

Самое простое, что можно сделать с помощью lit-html, — отобразить статический HTML.

import {html, render} from 'lit-html';

// Declare a template
const myTemplate = html`<div>Hello World</div>`;

// Render the template
render(myTemplate, document.body);

Шаблон lit-html — это шаблонный литерал с тегом. Сам шаблон выглядит как обычная строка JavaScript, но заключён в обратные кавычки (`), а не в кавычки. Браузер передаёт строку тег-функции html из lit-html.

Тег-функция html возвращает TemplateResult — облегчённый объект, представляющий шаблон для отображения.

Функция render создаёт узлы DOM и добавляет их в дерево DOM. В этом случае отображённый DOM заменяет содержимое тега body документа.

Отображение динамического текстового содержимого

Со статическим шаблоном далеко не уйти. lit-html позволяет создавать привязки с помощью заполнителей ${expression} в шаблонном литерале:

const aTemplate = html`<h1>${title}</h1>`;

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

import {html, render} from 'lit-html';

// Define a template function
const myTemplate = (name) => html`<div>Hello ${name}</div>`;

// Render the template with some data
render(myTemplate('world'), document.body);

// ... Later on ...
// Render the template with different data
render(myTemplate('lit-html'), document.body);

При вызове функции шаблона lit-html сохраняет текущие значения выражений. Функция шаблона не создаёт узлы DOM, поэтому работает быстро и требует мало ресурсов.

Функция шаблона возвращает TemplateResult, зависящий от входных данных. Это один из главных принципов использования lit-html: создание интерфейса как функции состояния.

При вызове render lit-html обновляет только те части шаблона, которые изменились после последнего отображения. Благодаря этому обновления lit-html выполняются очень быстро.

Использование выражений

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

const myTemplate = (subtotal, tax) => html`<div>Total: ${subtotal + tax}</div>`;
const myTemplate2 = (name) => html`<div>${formatName(name.given, name.family, name.title)}</div>`;

Привязка к атрибутам

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

По умолчанию выражение в значении атрибута создаёт привязку к атрибуту:

// set the class attribute
const myTemplate = (data) => html`<div class=${data.cssClass}>Stylish text.</div>`;

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

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

const myTemplate2 = (data) => html`<div ?disabled=${!data.active}>Stylish text.</div>`;

Привязка к свойствам

Можно также привязываться к свойствам JavaScript узла, используя префикс . и имя свойства:

const myTemplate3 = (data) => html`<input .value=${data.value}></input>`;

Привязки к свойствам позволяют передавать сложные данные по дереву вложенным компонентам. Например, если у вас есть компонент my-list со свойством listItems, ему можно передать массив объектов:

const myTemplate4 = (data) => html`<my-list .listItems=${data.items}></my-list>`;

Обратите внимание: имя свойства в этом примере — listItems — содержит символы разного регистра. Хотя регистр символов в атрибутах HTML не учитывается, lit-html сохраняет его при обработке шаблона.

Добавление обработчиков событий

Шаблоны также могут содержать декларативные обработчики событий. Обработчик события выглядит как привязка к атрибуту, но с префиксом @, за которым следует имя события:

const myTemplate = () => html`<button @click=${clickHandler}>Click Me!</button>`;

Это эквивалентно вызову addEventListener('click', clickHandler) для элемента кнопки.

Обработчиком события может быть обычная функция или объект с методом handleEvent:

const clickHandler = {
  // handleEvent method is required.
  handleEvent(e) {
    console.log('clicked!');
  },
  // event listener objects can also define zero or more of the event
  // listener options: capture, passive, and once.
  capture: true,
};

Объекты-обработчики событий. Если вы указываете обработчик с помощью объекта-обработчика события, сам объект-обработчик становится контекстом события (значением this).

Вложение и объединение шаблонов

Можно также объединять шаблоны, создавая более сложные шаблоны. Если привязка в текстовом содержимом шаблона возвращает TemplateResult, TemplateResult подставляется в это место.

const myHeader = html`<h1>Header</h1>`;
const myPage = html`
  ${myHeader}
  <div>Here's my main page.</div>
`;

Можно использовать любое выражение, возвращающее TemplateResult, например другую функцию шаблона:

// some complex view
const myListView = (items) => html`<ul>...</ul>`;

const myPage = (data) => html`
  ${myHeader}
  ${myListView(data.items)}
`;

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

Условные шаблоны

В lit-html нет встроенных конструкций управления потоком. Вместо них используются обычные выражения и инструкции JavaScript.

Условия с тернарными операторами

Тернарные выражения отлично подходят для добавления условий прямо в строку:

html`
  ${user.isloggedIn
      ? html`Welcome ${user.name}`
      : html`Please log in`
  }
`;

Условия с инструкциями if

Условную логику можно выразить с помощью инструкций if за пределами шаблона, вычисляя значения для использования внутри шаблона:

getUserMessage() {
  if (user.isloggedIn) {
    return html`Welcome ${user.name}`;
  } else {
    return html`Please log in`;
  }
}

html`
  ${getUserMessage()}
`

Повторяющиеся шаблоны

Для создания повторяющихся шаблонов можно использовать стандартные конструкции JavaScript.

В lit-html также есть специальные функции, называемые директивами, которые можно использовать в шаблонах. Директива repeat позволяет эффективнее создавать определённые типы динамических списков.

Повторяющиеся шаблоны с помощью Array.map

Для отображения списков можно использовать Array.map, чтобы преобразовать список данных в список шаблонов:

html`
  <ul>
    ${items.map((item) => html`<li>${item}</li>`)}
  </ul>
`;

Обратите внимание: это выражение возвращает массив объектов TemplateResult. lit-html отображает массив или итерируемую последовательность вложенных шаблонов и других значений.

Повторяющиеся шаблоны с помощью циклов

Можно также создать массив шаблонов и передать его в привязку шаблона.

const itemTemplates = [];
for (const i of items) {
  itemTemplates.push(html`<li>${i}</li>`);
}

html`
  <ul>
    ${itemTemplates}
  </ul>
`;

Повторяющиеся шаблоны с помощью директивы repeat

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

В этом случае может помочь директива repeat. Директивы — это специальные функции, обеспечивающие дополнительный контроль над отображением. В lit-html есть несколько встроенных директив, например repeat.

Директива repeat эффективно обновляет списки на основе предоставленных пользователем ключей:

repeat(items, keyFunction, itemTemplate)

Где:

  • items — это массив или итерируемая последовательность.
  • keyFunction — функция, которая принимает один элемент в качестве аргумента и возвращает гарантированно уникальный ключ для этого элемента.
  • itemTemplate — функция шаблона, принимающая в качестве аргументов элемент и его текущий индекс и возвращающая TemplateResult.

Например:

import {html} from 'lit-html';
import {repeat} from 'lit-html/directives/repeat.js';

const employeeList = (employees) => html`
  <ul>
    ${repeat(employees, (employee) => employee.id, (employee, index) => html`
      <li>${index}: ${employee.familyName}, ${employee.givenName}</li>
    `)}
  </ul>
`;

Если изменить порядок элементов в массиве employees, директива repeat переставит существующие узлы DOM.

Чтобы сравнить это с обработкой списков по умолчанию в lit-html, рассмотрим обращение большого списка имён:

  • Для списка, созданного с помощью Array.map, lit-html сохраняет узлы DOM элементов списка, но переназначает их значения.
  • Для списка, созданного с помощью repeat, директива repeat переставляет существующие узлы DOM, поэтому узлы, представляющие первый элемент списка, перемещаются на последнее место.

Выбор более эффективного способа повторения зависит от конкретной задачи: если обновление узлов DOM обходится дороже, чем их перемещение, используйте директиву repeat. В противном случае используйте Array.map или циклы.

Отображение пустого содержимого

Иногда может потребоваться не отображать ничего. Значения undefined, null и пустая строка ('') в текстовой привязке отображают пустой текстовый узел. В большинстве случаев это именно то, что нужно:

import {html} from 'lit-html';
${user.isAdmin
      ? html`<button>DELETE</button>`
      : ''
  }

В некоторых случаях требуется вообще ничего не отображать. Для этого можно использовать значение nothing, предоставляемое lit-html.

import {html, nothing} from 'lit-html';
${user.isAdmin
      ? html`<button>DELETE</button>`
      : nothing
  }

В этом случае, если user.isAdmin равно false, текстовый узел не отображается.

nothing и резервное содержимое слота

Один из конкретных случаев, когда пустой текстовый узел вызывает проблемы, — использование элемента <slot> внутри корня теневого DOM.

Этот случай специфичен для теневого DOM, и вы, вероятно, с ним не столкнётесь, если не используете lit-html в составе LitElement или другого базового класса веб-компонентов.

Представьте, что у вас есть пользовательский элемент example-element, содержащий слот в своём теневом DOM:

html`<slot>Sorry, no content available. I am just fallback content</slot>`;

Слот определяет резервное содержимое, которое отображается, если для слота не задано никакого содержимого.

Продолжим предыдущий пример:

import {nothing, html} from 'lit-html';

html`
<example-element>${user.isAdmin
        ? html`<button>DELETE</button>`
        : nothing
      }</example-element>
`;

Если пользователь вошёл в систему, отображается кнопка Delete. Если пользователь не вошёл в систему, внутри example-element ничего не отображается. Это значит, что слот пуст и отображается его резервное содержимое: «Извините, содержимое недоступно. Я — всего лишь резервное содержимое».

Если заменить nothing в этом примере пустой строкой, внутри example-element будет отображён пустой текстовый узел, из-за чего резервное содержимое не отобразится.

Пробелы создают текстовые узлы. Чтобы пример работал, текстовая привязка внутри <example-element> должна быть единственным содержимым <example-element>. Любые пробелы за пределами разделителей привязки добавляют в шаблон статические текстовые узлы, из-за чего резервное содержимое не отображается. При этом пробелы внутри разделителей привязки допустимы.

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

// Whitespace around the binding means the fallback content
// doesn't render
html`
<example-element> ${nothing} </example-element>
`;
// Line breaks count as whitespace, too
html`
<example-element>
${nothing}
</example-element>
`;

Кэширование результатов шаблона: директива cache

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

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

import {html} from 'lit-html';
import {cache} from 'lit-html/directives/cache.js';

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

html`${cache(data.showDetails
  ? detailView(data)
  : summaryView(data)
)}`

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

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

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

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

Spec-Zone.ru

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