Перейти к содержанию

Выражения

Шаблоны Lit могут включать динамические значения, называемые выражениями. Выражением может быть любое выражение JavaScript. Выражение оценивается, когда оценивается шаблон, и результат выражения включается при рендеринге шаблона. В компоненте Lit это означает, что метод render вызывается каждый раз.

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

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

Вот краткая справка, за которой следует более подробная информация о каждом типе выражений.

Дочерние узлы:

1
2
3
4
html` <h1>Hello ${name}</h1>
    <ul>
        ${listItems}
    </ul>`;

Атрибуты:

1
html`<div class=${highlightClass}></div>`;

Булевые атрибуты:

1
html`<div ?hidden=${!show}></div>`;

Свойства:

1
html`<input .value=${value} />`;

Слушатели событий:

1
html`<button @click=${this._clickHandler}>Go</button>`;

Директивы элементов:

1
html`<input ${ref(inputRef)} />`;

Этот базовый пример демонстрирует множество различных видов выражений.

В следующих разделах каждый вид выражения описан более подробно. Дополнительные сведения о структуре шаблонов см. в разделах Хорошо сформированный HTML и Допустимые места расположения выражений.

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

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

1
html`<p>Hello, ${name}</p>`;

Или:

1
html`<main>${bodyText}</main>`;

Выражения в дочерней позиции могут принимать различные значения:

  • Примитивные значения, такие как строки, числа и булевы.
  • Объекты TemplateResult, созданные с помощью функции html (или функции svg, если выражение находится внутри элемента <svg>).
  • Узлы DOM.
  • Значения sentinel nothing и noChange.
  • Массивы или итеративные таблицы любого из поддерживаемых типов.

Примитивные значения

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

Числовые значения, такие как 5, будут отображаться как строка `'5``. Бигинты обрабатываются аналогично.

Булево значение true будет отображаться как 'true', а false — как 'false', но такое отображение булевых значений встречается нечасто. Вместо этого булевы значения обычно используются в условных операторах для отображения других подходящих значений. Подробнее об условных выражениях см. в Conditionals.

Пустая строка '', null и undefined обрабатываются особым образом и ничего не отображают. Дополнительную информацию см. в разделе Удаление дочернего содержимого.

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

Значения посылок

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

Дозорное значение noChange не изменяет существующее значение выражения. Обычно оно используется в пользовательских директивах. Дополнительные сведения см. в разделе Сигнализация отсутствия изменений.

Сентинел nothing ничего не отображает. Дополнительную информацию см. в разделе Удаление дочернего содержимого.

Шаблоны

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

1
2
3
4
5
const nav = html`<nav>...</nav>`;
const page = html`
    ${nav}
    <main>...</main>
`;

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

1
2
3
4
5
html`
    ${this.user.isloggedIn
        ? html`Welcome ${this.user.name}`
        : html`Please log in`}
`;

Подробнее об условных знаках см. в разделе Условные операторы.

Подробнее об использовании JavaScript для создания повторяющихся шаблонов см. в Списки.

Узлы DOM

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

1
2
3
4
5
const div = document.createElement('div');
const page = html`
    ${div}
    <p>This is some text</p>
`;

Массивы или итерабельные таблицы любого из поддерживаемых типов

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

Удаление дочернего содержимого

Значения null, undefined, пустая строка '' и значение сентинеля Lit nothing удаляют все ранее отрисованное содержимое и не отрисовывают ни одного узла.

Установка или удаление дочернего содержимого часто выполняется на основе условия. Дополнительную информацию см. в разделе Conditionally rendering nothing.

Рендеринг no node может быть важен, когда выражение является дочерним элементом элемента с Shadow DOM, который включает slot с fallback-контентом. Рендеринг no node гарантирует, что содержимое fallback будет отображено. Дополнительную информацию см. в fallback content.

Выражения атрибутов

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

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

1
html`<div class=${this.textClass}>Stylish text.</div>`;

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

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

1
html`<img src="/images/${this.image}" />`;

Обратите внимание, что некоторые примитивные значения обрабатываются в атрибутах особым образом. Булевы значения преобразуются в строки, поэтому, например, false отображается как 'false'. Значения undefined и null отображаются в атрибуте как пустая строка.

Булевы атрибуты

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

1
2
3
html`<div ?hidden=${!this.showAdditional}>
    This text may be hidden.
</div>`;

Удаление атрибута

Иногда вы хотите установить атрибут только при определенных условиях, а в противном случае удалить его. Для обычных "булевых атрибутов", таких как disabled и hidden, где вы хотите установить атрибут в пустую строку для истинного значения и удалить его в противном случае, используйте булевый атрибут. Однако иногда вам может потребоваться другое условие для добавления или удаления атрибута.

Например:

1
2
3
html`<img
    src="/images/${this.imagePath}/${this.imageFile}"
/>`;

Если this.imagePath или this.imageFile не определены, атрибут src не должен быть установлен, иначе произойдет некорректный сетевой запрос.

Дозорное значение Lit nothing решает эту проблему, удаляя атрибут, когда любое выражение в значении атрибута оценивается как nothing.

1
2
3
4
html`<img
    src="/images/${this.imagePath ?? nothing}/${this
        .imageFile ?? nothing}"
/>`;

В данном примере для установки атрибута src должны быть определены оба** свойства this.imagePath и this.imageFile. Оператор ?? nullish coalescing operator возвращает правое значение, если левое значение null или undefined.

Lit также предоставляет директиву ifDefined, которая является сахаром для value ?? nothing.

1
2
3
4
5
html`<img
    src="/images/${ifDefined(this.imagePath)}/${ifDefined(
        this.imageFile,
    )}"
/>`;

Вы также можете захотеть удалить атрибут, если его значение не является истинным, так что значения false или пустая строка '' удаляют атрибут. Например, рассмотрим элемент, у которого значение по умолчанию для this.ariaLabel равно пустой строке '':

1
2
3
html`<button
    aria-label="${this.ariaLabel || nothing}"
></button>`;

В этом примере атрибут aria-label отображается только в том случае, если this.ariaLabel не является пустой строкой.

Установка или удаление атрибута часто выполняется на основе условия. Дополнительную информацию см. в Conditionally rendering nothing.

Выражения свойств

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

1
html`<input .value=${this.itemCount} />`;

Поведение приведенного выше кода аналогично прямому заданию свойства value для элемента input, например:

1
inputEl.value = this.itemCount;

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

1
html`<my-list .listItems=${this.items}></my-list>`;

Обратите внимание, что имя свойства в этом примере — listItems — имеет смешанный регистр. Хотя HTML атрибуты не чувствительны к регистру, Lit сохраняет регистр для имен свойств при обработке шаблона.

Более подробную информацию о свойствах компонентов можно найти в Reactive properties.

Выражения для слушателей событий

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

1
2
3
html`<button @click=${this.clickHandler}>
    Click Me!
</button>`;

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

Слушатель событий может быть как простой функцией, так и объектом с методом handleEvent — таким же, как аргумент listener в стандартном методе addEventListener.

В компоненте Lit слушатель событий автоматически привязывается к компоненту, поэтому вы можете использовать значение this внутри обработчика для ссылки на экземпляр компонента.

1
2
3
clickHandler() {
  this.clickCount++;
}

Более подробную информацию о событиях компонентов можно найти в Events.

Выражения для элементов

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

1
html`<div ${myDirective()}></div>`;

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

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

1
html`<button ${ref(this.myRef)}`;

Дополнительную информацию см. в ref.

Хорошо сформированный HTML

Шаблоны Lit должны быть хорошо сформированным HTML. Перед интерполяцией значений шаблоны разбираются встроенным HTML-парсером браузера. Следуйте этим правилам для хорошо сформированных шаблонов:

  • Шаблоны должны быть хорошо сформированным HTML, когда все выражения заменяются пустыми значениями.

  • Шаблоны могут содержать несколько элементов верхнего уровня и текст.

  • Шаблоны не должны содержать незакрытых элементов — они будут закрыты HTML-парсером.

    1
    2
    3
    4
    5
    6
    // HTML parser closes this div after "Some text"
    const template1 = html`<div class="broken-div">
        Some text
    </div>`;
    // When joined, "more text" does not end up in .broken-div
    const template2 = html`${template1} more text. </div>`;
    

Поскольку встроенный парсер браузера очень снисходителен, большинство случаев неправильного формирования шаблонов не обнаруживается во время выполнения, поэтому вы не увидите предупреждений — только шаблоны, которые ведут себя не так, как вы ожидаете. Мы рекомендуем использовать linting tools и IDE plugins для поиска проблем в ваших шаблонах во время разработки.

Допустимые места расположения выражений

Выражения могут встречаться только там, где вы можете разместить значения атрибутов и дочерних элементов в HTML.

1
2
3
4
5
6
7
8
<!-- attribute values -->
<div label="${label}"></div>
<button ?disabled="${isDisabled}">Click me!</button>
<input .value="${currentValue}" />
<button @click="${this.handleClick()}">
    <!-- child content -->
    <div>${textContent}</div>
</button>

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

1
<div ${ref(elementReference)}></div>

Недопустимые места

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

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

    1
    2
    3
    4
    5
    <!-- ERROR -->
    <${tagName}></${tagName}>
    
    <!-- ERROR -->
    <div ${attrName}=true></div>
    
  • Внутри содержимого элемента <template> (допускаются выражения атрибутов на самом элементе шаблона). Lit не выполняет рекурсию в содержимое шаблона для динамического обновления выражений и будет ошибаться в режиме разработки.

    1
    2
    3
    4
    5
    <!-- ERROR -->
    <template>${content}</template>
    
    <!-- OK -->
    <template id="${attrValue}">static content ok</template>
    
  • Внутри содержимого элемента <textarea> (допускаются выражения атрибутов для самого элемента textarea). Обратите внимание, что Lit может выводить содержимое в textarea, однако при редактировании textarea будут нарушены ссылки на DOM, которые Lit использует для динамического обновления, и Lit выдаст предупреждение в режиме разработки. Вместо этого привязывайтесь к свойству .value textarea.

    1
    2
    3
    4
    5
    6
    7
    8
    <!-- BEWARE -->
    <textarea>${content}</textarea>
    
    <!-- OK -->
    <textarea .value="${content}"></textarea>
    
    <!-- OK -->
    <textarea id="${attrValue}">static content ok</textarea>
    
  • Аналогично, внутри элементов с атрибутом contenteditable. Вместо этого привяжитесь к свойству .innerText элемента.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <!-- BEWARE -->
    <div contenteditable>${content}</div>
    
    <!-- OK -->
    <div contenteditable .innerText="${content}"></div>
    
    <!-- OK -->
    <div contenteditable id="${attrValue}">
        static content ok
    </div>
    
  • Внутри HTML-комментариев. Lit не будет обновлять выражения в комментариях, и вместо них будет отображаться строка маркеров Lit. Однако это не приведет к поломке последующих выражений, поэтому комментирование блоков HTML во время разработки, которые могут содержать выражения, безопасно.

    1
    <!-- will not update: ${value} -->
    
  • Внутри элементов <style> при использовании полифилла ShadyCSS. Подробнее см. в разделе Выражения и элементы стиля.

Обратите внимание, что выражения во всех приведенных выше недействительных случаях действительны при использовании статических выражений, хотя их не следует использовать для чувствительных к производительности обновлений из-за неэффективности (см. ниже).

Статические выражения

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

Чтобы использовать статические выражения, вы должны импортировать специальную версию тегов шаблонов html или svg из модуля Lit static-html:

1
import { html, literal } from 'lit/static-html.js';

Модуль static-html содержит функции тегов html и svg, которые поддерживают статические выражения и должны использоваться вместо стандартных версий, предоставляемых в модуле lit. Для создания статических выражений используйте функцию тега literal.

Вы можете использовать статические выражения для параметров конфигурации, которые вряд ли изменятся, или для настройки частей шаблона, которые нельзя использовать с помощью обычных выражений — подробнее см. в разделе Расположение допустимых выражений. Например, компонент my-button может отображать тег <button>, но его подкласс может вместо этого отображать тег <a>. Здесь хорошо использовать статическое выражение, потому что настройки меняются нечасто, а настройка HTML-тега не может быть выполнена с помощью обычного выражения.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { html, literal } from 'lit/static-html.js';

@customElement('my-button')
class MyButton extends LitElement {
    tag = literal`button`;
    activeAttribute = literal`active`;
    @property() caption = 'Hello static';
    @property({ type: Boolean }) active = false;

    render() {
        return html`
    <${this.tag} ${this.activeAttribute}=${this.active}>
        <p>${this.caption}</p>
    </${this.tag}>`;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { LitElement } from 'lit';
import { html, literal } from 'lit/static-html.js';

class MyButton extends LitElement {
    static properties = {
        caption: {},
        active: { type: Boolean },
    };

    tag = literal`button`;
    activeAttribute = literal`active`;

    constructor() {
        super();
        this.caption = 'Hello static';
        this.active = false;
    }

    render() {
        return html`
    <${this.tag} ${this.activeAttribute}=${this.active}>
        <p>${this.caption}</p>
    </${this.tag}>`;
    }
}
customElements.define('my-button', MyButton);

1
2
3
4
@customElement('my-anchor')
class MyAnchor extends MyButton {
    tag = literal`a`;
}
1
2
3
4
class MyAnchor extends MyButton {
    tag = literal`a`;
}
customElements.define('my-anchor', MyAnchor);

Изменение значений статических выражений требует больших затрат

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

В приведенном выше примере, если шаблон рендерится и изменяются this.caption или this.active, Lit обновляет шаблон эффективно, изменяя только затронутые выражения. Однако при изменении this.tag или this.activeAttribute, поскольку они являются статическими значениями, помеченными literal, создается совершенно новый шаблон; обновление неэффективно, поскольку DOM полностью перерисовывается. Кроме того, изменение значений literal, передаваемых выражениям, увеличивает использование памяти, поскольку каждый уникальный шаблон кэшируется в памяти для повышения производительности повторного рендеринга.

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

Структура шаблона

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

Нелитеральная статика

В редких случаях вам может понадобиться интерполировать статический HTML в шаблон, который не определен в вашем скрипте и поэтому не может быть помечен функцией literal. Для таких случаев можно использовать функцию unsafeStatic() для создания статического HTML на основе строк из нескриптовых источников.

1
import { html, unsafeStatic } from 'lit/static-html.js';

Только для доверенного содержимого

Обратите внимание на использование unsafe в unsafeStatic(). Строка, передаваемая в unsafeStatic(), должна контролироваться разработчиком и не содержать недоверенного содержимого, поскольку она будет разобрана непосредственно как HTML без какой-либо санитарной обработки. Примерами недоверенного содержимого являются параметры строки запроса и значения из пользовательского ввода. Недоверенное содержимое, отображаемое с помощью этой директивы, может привести к межсайтовому скриптингу (XSS) уязвимостям.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@customElement('my-button')
class MyButton extends LitElement {
    @property() caption = 'Hello static';
    @property({ type: Boolean }) active = false;

    render() {
        // These strings MUST be trusted, otherwise this is an XSS vulnerability
        const tag = getTagName();
        const activeAttribute = getActiveAttribute();
        return html`
    <${unsafeStatic(tag)} ${unsafeStatic(
        activeAttribute,
    )}=${this.active}>
        <p>${this.caption}</p>
    </${unsafeStatic(tag)}>`;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MyButton extends LitElement {
    static properties = {
        caption: {},
        active: { type: Boolean },
    };

    constructor() {
        super();
        this.caption = 'Hello static';
        this.active = false;
    }

    render() {
        // These strings MUST be trusted, otherwise this is an XSS vulnerability
        const tag = getTagName();
        const activeAttribute = getActiveAttribute();
        return html`
    <${unsafeStatic(tag)} ${unsafeStatic(
        activeAttribute,
    )}=${this.active}>
        <p>${this.caption}</p>
    </${unsafeStatic(tag)}>`;
    }
}
customElements.define('my-button', MyButton);

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

Комментарии