表达式
Lit 模板可以包含称为表达式的动态值。表达式可以是任何 JavaScript 表达式。当模板被评估时,表达式会被计算,并且表达式的结果会在模板渲染时被包含进来。在 Lit 组件中,这意味着每当 render
方法被调用时,表达式都会被重新计算。
表达式只能放置在模板中的特定位置,而且表达式的解释方式取决于它出现的位置。元素标签内部的表达式会影响元素本身。元素内容中的表达式(即子节点所在的位置)会渲染成子节点或文本。
表达式的有效值根据表达式出现的位置而不同。一般来说,所有表达式都接受字符串和数字等原始值,而某些表达式支持额外的值类型。此外,所有表达式都可以接受_指令_,这些指令是定制表达式处理和渲染方式的特殊函数。有关更多信息,请参阅自定义指令。
下面是一个快速参考,随后是关于每种表达式类型的更详细信息。
类型 | 示例 |
---|---|
| |
| |
| |
| |
| |
|
下面的基本示例展示了不同类型的表达式。
以下部分详细描述了每种表达式类型。有关模板结构的更多信息,请参阅格式良好的 HTML和有效的表达式位置。
在元素的开始标签和结束标签之间出现的表达式可以向元素添加子节点。例如:
html`<p>Hello, ${name}</p>`
或者:
html`<main>${bodyText}</main>`
子位置的表达式可以接受多种类型的值:
- 原始值,如字符串、数字和布尔值。
- 使用
html
函数创建的TemplateResult
对象(如果表达式在<svg>
元素内,则使用svg
函数)。 - DOM 节点。
- 哨兵值
nothing
和noChange
。 - 任何支持类型的数组或可迭代对象。
Lit 可以渲染几乎所有原始值,并在插入到文本内容时将它们转换为字符串。
数字值如 5
将渲染为字符串 '5'
。大整数(Bigint)也类似处理。
布尔值 true
将渲染为 'true'
,而 false
将渲染为 'false'
,但这种直接渲染布尔值的情况并不常见。通常,布尔值会在条件语句中用于渲染其他适当的值。有关条件的更多信息,请参阅条件渲染。
空字符串 ''
、null
和 undefined
会被特殊处理,不会渲染任何内容。有关更多信息,请参阅移除子内容。
Symbol 值无法转换为字符串,在子表达式中使用时会抛出错误。
Lit 提供了几个可以在子表达式中使用的特殊哨兵值。
noChange
哨兵值不会改变表达式的现有值。它通常用于自定义指令中。有关更多信息,请参阅表示无变化。
nothing
哨兵值不渲染任何内容。有关更多信息,请参阅移除子内容。
由于子位置的表达式可以返回 TemplateResult
,你可以嵌套和组合模板:
const nav = html`<nav>...</nav>`;
const page = html`
${nav}
<main>...</main>
`;
这意味着你可以使用普通的 JavaScript 创建条件模板、重复模板等。
html`
${this.user.isloggedIn
? html`Welcome ${this.user.name}`
: html`Please log in`
}
`;
有关条件的更多信息,请参阅条件渲染。
有关使用 JavaScript 创建重复模板的更多信息,请参阅列表。
DOM 节点
Permalink to "DOM 节点"任何 DOM 节点都可以传递给子表达式。通常,DOM 节点应该通过使用 html
指定模板来渲染,但在需要时可以像这样直接渲染 DOM 节点。节点会在该点附加到 DOM 树上,因此会从其当前父节点中移除:
const div = document.createElement('div');
const page = html`
${div}
<p>This is some text</p>
`;
任何支持类型的数组或可迭代对象
Permalink to "任何支持类型的数组或可迭代对象"表达式还可以返回任何支持类型的数组或可迭代对象,可以是任意组合。你可以将此功能与标准 JavaScript(如数组的 map
方法)一起使用来创建重复模板和列表。有关示例,请参阅列表。
移除子内容
Permalink to "移除子内容"值 null
、undefined
、空字符串 ''
和 Lit 的 nothing 哨兵值会移除任何先前渲染的内容,且不渲染任何节点。
设置或移除子内容通常基于条件进行。有关更多信息,请参阅条件渲染为空。
当表达式是带有 Shadow DOM 的元素的子元素,且该元素包含带有回退内容的 slot
时,不渲染节点非常重要。不渲染节点可确保渲染回退内容。有关更多信息,请参阅回退内容。
属性表达式
Permalink to "属性表达式"除了使用表达式添加子节点外,你还可以使用它们来设置元素的特性(attribute)和属性(property)。
默认情况下,属性值中的表达式会设置该属性:
html`<div class=${this.textClass}>Stylish text.</div>`;
由于属性值始终是字符串,表达式应返回可转换为字符串的值。
如果表达式构成整个属性值,则可以省略引号。如果表达式仅构成属性值的一部分,则需要为整个值加引号:
html`<img src="/images/${this.image}">`;
注意,某些原始值在属性中会特殊处理。布尔值会转换为字符串,例如 false
渲染为 'false'
。undefined
和 null
作为属性会渲染为空字符串。
要设置布尔属性,请在属性名称前使用 ?
前缀。如果表达式计算结果为真值,则添加属性;如果为假值,则移除属性:
html`<div ?hidden=${!this.showAdditional}>This text may be hidden.</div>`;
有时你可能只想在特定条件下设置属性,否则移除该属性。对于常见的"布尔属性",如 disabled
和 hidden
,如果你希望在为真值时将属性设置为空字符串,否则将其移除,请使用布尔属性。但有时,你可能需要不同的条件来添加或移除属性。
例如,考虑:
html`<img src="/images/${this.imagePath}/${this.imageFile}">`;
如果 this.imagePath
或 this.imageFile
未定义,则不应设置 src
属性,否则会发生无效的网络请求。
Lit 的 nothing 哨兵值通过在属性值中的任何表达式计算为 nothing
时移除属性来解决这个问题。
html`<img src="/images/${this.imagePath ?? nothing}/${this.imageFile ?? nothing}">`;
在此示例中,同时需要定义 this.imagePath
和 this.imageFile
属性才能设置 src
属性。??
空值合并运算符在左侧值为 null
或 undefined
时返回右侧值。
Lit 还提供了 ifDefined 指令,它是 value ?? nothing
的语法糖。
html`<img src="/images/${ifDefined(this.imagePath)}/${ifDefined(this.imageFile)}">`;
你可能还希望在值不为真时移除属性,以便 false
或空字符串 ''
值移除属性。例如,考虑一个 this.ariaLabel
默认值为空字符串 ''
的元素:
html`<button aria-label="${this.ariaLabel || nothing}"></button>`
在此示例中,仅当 this.ariaLabel
不是空字符串时才渲染 aria-label
属性。
设置或移除属性通常基于条件进行。有关更多信息,请参阅条件渲染为空。
属性(Property)表达式
Permalink to "属性(Property)表达式"你可以使用 .
前缀和属性名称在元素上设置 JavaScript 属性:
html`<input .value=${this.itemCount}>`;
上面代码的行为与直接设置 input
元素上的 value
属性相同,例如:
inputEl.value = this.itemCount;
你可以使用属性表达式语法将复杂数据向下传递给子组件。例如,如果你有一个带有 listItems
属性的 my-list
组件,你可以传递一个对象数组给它:
html`<my-list .listItems=${this.items}></my-list>`;
请注意,此示例中的属性名称 listItems
是混合大小写的。虽然 HTML 属性不区分大小写,但 Lit 在处理模板时会保留属性名称的大小写。
有关组件属性的更多信息,请参阅响应式属性。
事件监听器表达式
Permalink to "事件监听器表达式"模板还可以包含声明式事件监听器。使用前缀 @
后跟事件名称。表达式应计算为事件监听器。
html`<button @click=${this.clickHandler}>Click Me!</button>`;
这类似于在按钮元素上调用 addEventListener('click', this.clickHandler)
。
事件监听器可以是普通函数,也可以是具有 handleEvent
方法的对象 — 与标准 addEventListener
方法的 listener
参数相同。
在 Lit 组件中,事件监听器会自动绑定到组件,因此你可以在处理程序中使用 this
值引用组件实例。
clickHandler() {
this.clickCount++;
}
有关组件事件的更多信息,请参阅事件。
元素表达式
Permalink to "元素表达式"你还可以添加一个表达式来访问元素实例,而不是元素上的单个属性或特性:
html`<div ${myDirective()}></div>`
元素表达式仅适用于指令。元素表达式中的任何其他值类型都会被忽略。
可以在元素表达式中使用的一个内置指令是 ref
指令。它提供对渲染元素的引用。
html`<button ${ref(this.myRef)}></button>`;
有关更多信息,请参阅 ref。
格式良好的 HTML
Permalink to "格式良好的 HTML"Lit 模板必须是格式良好的 HTML。在插值任何值之前,模板会由浏览器内置的 HTML 解析器解析。请遵循以下规则制作格式良好的模板:
当所有表达式都替换为空值时,模板必须是格式良好的 HTML。
模板可以有多个顶级元素和文本。
模板_不应包含_未闭合的元素 — 它们将被 HTML 解析器关闭。
// HTML 解析器在 "Some text" 后关闭此 div
const template1 = html`<div class="broken-div">Some text`;
// 连接时,"more text" 不会出现在 .broken-div 中
const template2 = html`${template1} more text. </div>`;
有效的表达式位置
Permalink to "有效的表达式位置"表达式**只能出现**在 HTML 中可以放置属性值和子元素的位置。
<!-- 属性值 -->
<div label=${label}></div>
<button ?disabled=${isDisabled}>Click me!</button>
<input .value=${currentValue}>
<button @click=${this.handleClick()}>
<!-- 子内容 -->
<div>${textContent}</div>
元素表达式可以出现在开始标签中,位于标签名称之后:
<div ${ref(elementReference)}></div>
表达式通常不应出现在以下位置:
标签或属性名称的位置。Lit 不支持在此位置动态更改值,在开发模式下会产生错误。
<!-- 错误 -->
<${tagName}></${tagName}>
<!-- 错误 -->
<div ${attrName}=true></div>
<template>
元素内容内部(template 元素本身的属性表达式是允许的)。Lit 不会递归进入模板内容以动态更新表达式,在开发模式下会产生错误。<!-- 错误 -->
<template>${content}</template>
<!-- 可以 -->
<template id="${attrValue}">static content ok</template>
<textarea>
元素内容内部(textarea 元素本身的属性表达式是允许的)。请注意,Lit 可以向 textarea 渲染内容,但编辑 textarea 会破坏 Lit 用于动态更新的 DOM 引用,且 Lit 会在开发模式下发出警告。相反,应绑定到 textarea 的.value
属性。<!-- 注意 -->
<textarea>${content}</textarea>
<!-- 可以 -->
<textarea .value=${content}></textarea>
<!-- 可以 -->
<textarea id="${attrValue}">static content ok</textarea>
类似地,在带有
contenteditable
属性的元素内部。相反,应绑定到元素的.innerText
属性。<!-- 注意 -->
<div contenteditable>${content}</div>
<!-- 可以 -->
<div contenteditable .innerText=${content}></div>
<!-- 可以 -->
<div contenteditable id="${attrValue}">static content ok</div>
HTML 注释内部。Lit 不会更新注释中的表达式,表达式将以 Lit 标记字符串渲染。但这不会破坏后续表达式,因此在开发过程中注释掉可能包含表达式的 HTML 块是安全的。
<!-- 不会更新:${value} -->
使用 ShadyCSS polyfill 时,在
<style>
元素内部。有关更多详细信息,请参阅表达式和样式元素。
请注意,在使用静态表达式时,上述所有无效情况中的表达式都是有效的,尽管由于涉及效率低下(见下文),不应将其用于性能敏感的更新。
静态表达式
Permalink to "静态表达式"静态表达式返回特殊值,这些值在模板被 Lit 作为 HTML 处理之前就插入到模板中。由于它们成为模板静态 HTML 的一部分,它们可以放置在模板的任何位置 - 甚至是那些通常不允许表达式的位置,例如属性和标签名称。
要使用静态表达式,你必须从 Lit 的 static-html
模块导入特殊版本的 html
或 svg
模板标签:
import {html, literal} from 'lit/static-html.js';
static-html
模块包含支持静态表达式的 html
和 svg
标签函数,应该使用这些函数来代替 lit
模块中提供的标准版本。使用 literal
标签函数创建静态表达式。
你可以将静态表达式用于不太可能更改的配置选项,或用于自定义使用普通表达式无法自定义的模板部分 - 有关详细信息,请参阅有效的表达式位置部分。例如,my-button
组件可能渲染一个 <button>
标签,但子类可能渲染一个 <a>
标签。这是使用静态表达式的好地方,因为该设置不会频繁更改,而且无法使用普通表达式自定义 HTML 标签。
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}>`;
}
}
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);
@customElement('my-anchor')
class MyAnchor extends MyButton {
tag = literal`a`;
}
class MyAnchor extends MyButton {
tag = literal`a`;
}
customElements.define('my-anchor', MyAnchor);
更改静态表达式的值代价很高。 使用 literal
值的表达式不应频繁更改,因为它们会导致重新解析新模板,并且每个变体都会保存在内存中。
在上面的示例中,如果模板重新渲染,且 this.caption
或 this.active
发生变化,Lit 会高效地更新模板,只更改受影响的表达式。但是,如果 this.tag
或 this.activeAttribute
发生变化,由于它们是用 literal
标记的静态值,会创建一个全新的模板;更新效率低下,因为会完全重新渲染 DOM。此外,更改传递给表达式的 literal
值会增加内存使用量,因为每个唯一模板都会缓存在内存中以提高重新渲染性能。
因此,最好将使用 literal
的表达式的更改保持在最低限度,并避免使用响应式属性来更改 literal
值,因为响应式属性本来就是为了可以更改而设计的。
静态值插值后,模板必须像普通 Lit 模板一样格式良好,否则模板中的动态表达式可能无法正常工作。有关更多信息,请参阅格式良好的 HTML部分。
非字面量静态值
Permalink to "非字面量静态值"在极少数情况下,你可能需要将未在脚本中定义的静态 HTML 插入到模板中,因此无法使用 literal
函数标记。对于这些情况,可以使用 unsafeStatic()
函数基于非脚本源的字符串创建静态 HTML。
import {html, unsafeStatic} from 'lit/static-html.js';
仅用于可信内容。 注意 unsafeStatic()
中使用了 unsafe。传递给 unsafeStatic()
的字符串必须由开发者控制,不包含不可信内容,因为它将直接作为 HTML 解析,没有任何净化处理。不可信内容的例子包括查询字符串参数和用户输入的值。使用此指令渲染不可信内容可能导致 跨站脚本攻击 (XSS) 漏洞。
@customElement('my-button')
class MyButton extends LitElement {
@property() caption = 'Hello static';
@property({type: Boolean}) active = false;
render() {
// 这些字符串必须是可信的,否则这将是一个 XSS 漏洞
const tag = getTagName();
const activeAttribute = getActiveAttribute();
// html 应该从 `lit/static-html.js` 导入
return html`
<${unsafeStatic(tag)} ${unsafeStatic(activeAttribute)}=${this.active}>
<p>${this.caption}</p>
</${unsafeStatic(tag)}>`;
}
}
class MyButton extends LitElement {
static properties = {
caption: {},
active: {type: Boolean},
};
constructor() {
super();
this.caption = 'Hello static';
this.active = false;
}
render() {
// 这些字符串必须是可信的,否则这将是一个 XSS 漏洞
const tag = getTagName();
const activeAttribute = getActiveAttribute();
// html 应该从 `lit/static-html.js` 导入
return html`
<${unsafeStatic(tag)} ${unsafeStatic(activeAttribute)}=${this.active}>
<p>${this.caption}</p>
</${unsafeStatic(tag)}>`;
}
}
customElements.define('my-button', MyButton);
请注意,使用 unsafeStatic
的行为与 literal
具有相同的注意事项:由于更改值会导致解析新模板并将其缓存在内存中,因此它们不应频繁更改。