Написание шаблонов
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/