Справочник по синтаксису шаблонов
Шаблоны lit-html записываются с помощью шаблонных литералов JavaScript, помеченных тегом html. Содержимое литерала — это в основном обычный декларативный HTML:
html`<h1>Hello World</h1>`
Привязки или выражения обозначаются стандартным синтаксисом JavaScript для шаблонных литералов:
html`<h1>Hello ${name}</h1>`
Структура шаблона
Шаблоны lit-html должны быть корректно сформированным HTML, а привязки могут находиться только в определённых местах. Браузер разбирает шаблоны встроенным HTML-парсером до интерполяции каких-либо значений.
Предупреждений не будет. Большинство случаев некорректных шаблонов не обнаруживаются lit-html, поэтому вы не увидите предупреждений — только шаблоны, которые работают не так, как вы ожидаете. Поэтому уделяйте особое внимание правильной структуре шаблонов.
Чтобы шаблоны были корректно сформированы, следуйте этим правилам:
Шаблоны должны представлять собой корректно сформированный HTML, если заменить все выражения пустыми значениями.
-
Привязки могут находиться только в значениях атрибутов и текстовом содержимом.
<!-- attribute value --> <div label="${label}"></div> <!-- text content --> <div>${textContent}</div> -
Выражения не могут находиться там, где должны быть имена тегов или атрибутов.
<!-- ERROR --> <${tagName}></${tagName}> <!-- ERROR --> <div ${attrName}=true></div> Шаблоны могут содержать несколько элементов и текст на верхнем уровне.
-
Шаблоны не должны содержать незакрытых элементов — HTML-парсер закроет их автоматически.
// HTML parser closes this div after "Some text" const template1 = html`<div class="broken-div">Some text`; // When joined, "more text" does not end up in .broken-div const template2 = html`${template1} more text. </div>`;
Типы привязок
Выражения могут находиться в текстовом содержимом или в значениях атрибутов.
Существует несколько типов привязок:
-
Текстовая:
html`<h1>Hello ${name}</h1>`Текстовые привязки могут находиться в любом месте текстового содержимого элемента.
-
Атрибутная:
html`<div id=${id}></div>` -
Булев атрибут:
html`<input type="checkbox" ?checked=${checked}>` -
Свойство:
html`<input .value=${value}>` -
Обработчик события:
html`<button @click=${(e) => console.log('clicked')}>Click Me</button>`
Обработчики событий
Обработчиками событий могут быть функции или объекты с методом handleEvent. Обработчики передаются в качестве аргументов listener и options в addEventListener/removeEventListener, поэтому обработчик может содержать параметры обработчика событий, например capture, passive и once.
const listener = {
handleEvent(e) {
console.log('clicked');
},
capture: true,
};
html`<button @click=${listener}>Click Me</button>`
Поддерживаемые типы данных
Каждый тип привязки поддерживает разные типы значений:
Привязки текстового содержимого: множество типов, см. раздел Поддерживаемые типы данных для текстовых привязок.
Атрибутные привязки: все значения преобразуются в строки.
Привязки булевых атрибутов: все значения проверяются на истинность.
Привязки свойств: значения любого типа.
Привязки обработчиков событий: только функции или объекты — обработчики событий.
Поддерживаемые типы данных для текстовых привязок
Текстовые привязки поддерживают широкий диапазон типов значений:
- Примитивные значения.
- Объекты TemplateResult.
- Узлы DOM.
- Массивы или итерируемые объекты.
Примитивные значения: String, Number, Boolean, null, undefined
При интерполяции в текстовое содержимое или значения атрибутов примитивные значения преобразуются в строки. Они сравниваются с предыдущим значением, чтобы не обновлять DOM, если значение не изменилось.
TemplateResult
Шаблоны можно вкладывать друг в друга, передав TemplateResult в качестве значения выражения:
const header = html`<h1>Header</h1>`;
const page = html`
${header}
<p>This is some text</p>
`;
Узел
В выражение в текстовой позиции можно передать любой узел DOM. Узел будет добавлен в дерево DOM в указанном месте и удалён из текущего родителя:
const div = document.createElement('div');
const page = html`
${div}
<p>This is some text</p>
`;
Массивы / итерируемые объекты
Также поддерживаются массивы и итерируемые объекты, содержащие значения поддерживаемых типов. Они могут включать значения разных поддерживаемых типов.
const items = [1, 2, 3];
const list = () => html`items = ${items.map((i) => `item: ${i}`)}`;
const items = {
a: 1,
b: 23,
c: 456,
};
const list = () => html`items = ${Object.entries(items)}`;
Управление потоком выполнения с помощью JavaScript
В 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()}
`
Циклы с Array.map
Для отображения списков можно использовать Array.map, чтобы преобразовать список данных в список шаблонов:
html`
<ul>
${items.map((i) => html`<li>${i}</li>`)}
</ul>
`;
Циклические инструкции
const itemTemplates = [];
for (const i of items) {
itemTemplates.push(html`<li>${i}</li>`);
}
html`
<ul>
${itemTemplates}
</ul>
`;
Встроенные директивы
Директивы — это функции, расширяющие возможности lit-html за счёт настройки способа отображения привязки.
В lit-html есть несколько встроенных директив.
asyncAppend и asyncReplace
asyncAppend(asyncIterable)asyncReplace(asyncIterable)
Расположение: текстовые привязки
Асинхронные итераторы JavaScript предоставляют универсальный интерфейс для последовательного асинхронного доступа к данным. Как и в случае с обычным итератором, потребитель запрашивает следующий элемент данных вызовом next(), но у асинхронных итераторов next() возвращает Promise, что позволяет итератору предоставить элемент, когда он будет готов.
В lit-html есть две директивы для обработки асинхронных итераторов:
asyncAppendотображает значения асинхронно итерируемого объекта, добавляя каждое новое значение после предыдущего.asyncReplaceотображает значения асинхронно итерируемого объекта, заменяя предыдущее значение новым.
Пример:
import {asyncReplace} from 'lit-html/directives/async-replace.js';
const wait = (t) => new Promise((resolve) => setTimeout(resolve, t));
/**
* Returns an async iterable that yields increasing integers.
*/
async function* countUp() {
let i = 0;
while (true) {
yield i++;
await wait(1000);
}
}
render(html`
Count: <span>${asyncReplace(countUp())}</span>.
`, document.body);
В ближайшем будущем ReadableStream станут асинхронно итерируемыми объектами, что позволит напрямую передавать потоковые fetch() в шаблон:
import {asyncAppend} from 'lit-html/directives/async-append.js';
// Endpoint that returns a billion digits of PI, streamed.
const url =
'https://cors-anywhere.herokuapp.com/http://stuff.mit.edu/afs/sipb/contrib/pi/pi-billion.txt';
const streamingResponse = (async () => {
const response = await fetch(url);
return response.body.getReader();
})();
render(html`π is: ${asyncAppend(streamingResponse)}`, document.body);
cache
cache(conditionalTemplate)
Расположение: текстовые привязки
Кэширует отображённые узлы DOM шаблонов, когда они не используются. Аргумент conditionalTemplate — это выражение, которое может возвращать один из нескольких шаблонов. cache отображает текущее значение conditionalTemplate. При смене шаблона директива кэширует текущие узлы DOM, прежде чем переключиться на новое значение.
Пример:
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 достаточно подставить кэшированную версию нового представления и обновить её последними данными.
classMap
class=${classMap(classObj)}
Расположение: атрибутные привязки (должна быть единственной привязкой в атрибуте class)
Задаёт список классов на основе объекта. Каждый ключ объекта считается именем класса. Если соответствующее ключу значение истинно, этот класс добавляется элементу.
import {classMap} from 'lit-html/directives/class-map.js';
let classes = { highlight: true, enabled: true, hidden: false };
html`<div class=${classMap(classes)}>Classy text</div>`;
// renders as <div class="highlight enabled">Classy text</div>
classMap должна быть единственной привязкой в атрибуте class, но её можно сочетать со статическими значениями:
html`<div class="my-widget ${classMap(dynamicClasses)}">Static and dynamic</div>`;
ifDefined
ifDefined(value)
Расположение: атрибутные привязки
Для AttributeParts: задаёт атрибут, если значение определено, и удаляет его, если значение равно undefined.
Для других типов частей эта директива ничего не делает.
Пример:
import {ifDefined} from 'lit-html/directives/if-defined';
const myTemplate = () => html`
<img src="/images/${ifDefined(image.filename)}">
`;
guard
guard(dependencies, valueFn)
Расположение: любое
Отображает значение, возвращаемое valueFn. Повторно вычисляет valueFn, только если идентичность одной из зависимостей изменилась.
Где:
-
dependencies— массив значений, изменения которых нужно отслеживать. (Для обратной совместимостиdependenciesможет быть одним значением, не являющимся массивом.) -
valueFn— функция, возвращающая значение, которое можно отобразить.
guard полезна при использовании неизменяемых структур данных: она позволяет отложить затратные операции до обновления данных.
Пример:
import {guard} from 'lit-html/directives/guard';
const template = html`
<div>
${guard([immutableItems], () => immutableItems.map(item => html`${item}`))}
</div>
`;
В этом случае массив immutableItems перебирается только при изменении ссылки на массив.
live
attr=${live(value)}
Расположение: атрибутные привязки или привязки свойств
При определении необходимости обновить значение сравнивает значение привязки с текущим значением в DOM, а не с ранее привязанным значением.
Это полезно в случаях, когда значение в DOM может измениться вне lit-html. Например, при привязке к свойству value элемента <input>, к тексту элемента с возможностью редактирования или к пользовательскому элементу, который изменяет собственные свойства или атрибуты.
В таких случаях, если значение в DOM изменилось, а значение, заданное с помощью привязок lit-html, — нет, lit-html не узнает, что значение в DOM нужно обновить, и оставит его без изменений. Если это не то, что вам нужно, и вы хотите в любом случае перезаписать значение в DOM привязанным значением, используйте директиву live().
Пример:
html`<input .value=${live(x)}>`
live() выполняет строгое сравнение с текущим значением в DOM и ничего не делает, если новое значение совпадает с ним. Это означает, что live() не следует использовать, если привязка вызывает преобразование типа. При использовании live() с атрибутной привязкой передавайте только строки, иначе привязка будет обновляться при каждом отображении.
repeat
repeat(items, keyfn, template)repeat(items, template)
Расположение: текстовые привязки
Повторяет последовательность значений (обычно TemplateResults), сформированных из итерируемого объекта, и эффективно обновляет эти элементы при изменении итерируемого объекта. Если задан keyFn, при обновлениях сохраняется соответствие ключей узлам DOM, а при необходимости узлы перемещаются. Как правило, это наиболее эффективный способ использовать repeat, поскольку он сводит к минимуму лишнюю работу при добавлении и удалении элементов.
Пример:
import {repeat} from 'lit-html/directives/repeat';
const myTemplate = () => html`
<ul>
${repeat(items, (i) => i.id, (i, index) => html`
<li>${index}: ${i.name}</li>`)}
</ul>
`;
Если keyFn не задан, repeat будет работать подобно обычному сопоставлению элементов со значениями, а узлы DOM могут повторно использоваться для других элементов.
О том, когда использовать repeat, а когда — стандартные средства управления потоком выполнения JavaScript, см. раздел Повторяющиеся шаблоны с директивой repeat.
styleMap
style=${styleMap(styles)}
Расположение: атрибутные привязки (должна быть единственной привязкой в атрибуте style)
Директива styleMap задаёт стили элемента на основе объекта. Каждый ключ объекта считается свойством стиля, а значение — значением этого свойства. Например:
import {styleMap} from 'lit-html/directives/style-map.js';
let styles = { backgroundColor: 'blue', color: 'white' };
html`<p style=${styleMap(styles)}>Hello style!</p>`;
Для свойств CSS с дефисами можно использовать эквивалентное имя в camelCase или заключить имя свойства в кавычки. Например, свойство CSS font-family можно записать как fontFamily или 'font-family':
{ fontFamily: 'roboto' }
{ 'font-family': 'roboto' }
styleMap должна быть единственной привязкой в атрибуте style, но её можно сочетать со статическими значениями:
html`<p style="color: white; ${styleMap(moreStyles)}">More styles!</p>`;
templateContent
templateContent(templateElement)
Расположение: текстовые привязки
Отображает содержимое элемента <template> как HTML.
Обратите внимание: содержимое шаблона должно контролироваться разработчиком, а не пользователем. Отображение пользовательских шаблонов с помощью этой директивы может привести к уязвимостям XSS.
Пример:
import {templateContent} from 'lit-html/directives/template-content';
const templateEl = document.querySelector('template#myContent');
const template = html`
Here's some content from a template element:
${templateContent(templateEl)}`;
unsafeHTML
unsafeHTML(html)
Расположение: текстовые привязки
Отображает аргумент как HTML, а не как текст.
Обратите внимание: небезопасно использовать эту директиву с пользовательскими данными, которые не были очищены или экранированы, поскольку это может привести к уязвимостям межсайтового скриптинга.
Пример:
import {unsafeHTML} from 'lit-html/directives/unsafe-html.js';
const markup = '<div>Some HTML to render.</div>';
const template = html`
Look out, potentially unsafe HTML ahead:
${unsafeHTML(markup)}
`;
unsafeSVG
unsafeSVG(svg)
Расположение: текстовые привязки
Отображает аргумент как SVG, а не как текст.
Обратите внимание: небезопасно использовать эту директиву с пользовательскими данными, которые не были очищены или экранированы, поскольку это может привести к уязвимостям межсайтового скриптинга.
Пример:
import {unsafeSVG} from 'lit-html/directives/unsafe-svg';
const svg = '<circle cx="50" cy="50" r="40" fill="red" />'
const template = 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> `;
until
until(...values)
Расположение: любое
Отображает содержимое-заполнитель, пока не будет доступно окончательное содержимое.
Принимает последовательность значений, в том числе Promise. Значения отображаются в порядке приоритета: первый аргумент имеет наивысший приоритет, а последний — наименьший. Если значение является Promise, до его выполнения будет отображаться значение с более низким приоритетом.
Приоритет значений можно использовать для создания содержимого-заполнителя для асинхронных данных. Например, Promise с ожидающим содержимым можно передать первым аргументом с наивысшим приоритетом, а шаблон без Promise с индикатором загрузки — вторым аргументом с более низким приоритетом. Индикатор загрузки отображается сразу, а основное содержимое — после выполнения Promise.
Пример:
import {until} from 'lit-html/directives/until.js';
const content = fetch('./content.txt').then(r => r.text());
html`${until(content, html`<span>Loading...</span>`)}`
© Google LLC
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://lit.dev/docs/v1/lit-html/template-reference/