表达式
Lit 模板可以包含称为表达式的动态值。表达式可以是任意 JavaScript 表达式。表达式在模板被求值时进行计算,其结果会包含在模板渲染中。在 Lit 组件中,这意味着每次调用 render 方法时都会进行计算。
表达式只能放置在模板中的特定位置,并且表达式的解释方式取决于它出现的位置。元素标签本身内部的表达式会影响该元素。元素内容内部(即子节点所在的位置)的表达式会渲染子节点或文本。
表达式的有效值因表达式出现的位置而异。通常,所有表达式都接受字符串和数字等原始值,部分表达式还支持额外的值类型。此外,所有表达式都可以接受_指令_,即自定义表达式处理和渲染方式的特殊函数。更多信息请参阅自定义指令。
以下是快速参考,随后是关于每种表达式类型的更详细信息。
| Type | 示例 |
|---|---|
| |
| |
| |
| |
| |
|
以下基本示例展示了多种不同类型的表达式。
以下各节将更详细地描述每种表达式类型。有关模板结构的更多信息,请参阅合规的 HTML 和有效的表达式位置。
出现在元素的开始标签和结束标签之间的表达式可以向该元素添加子节点。例如:
html`<p>Hello, ${name}</p>` 或者:
html`<main>${bodyText}</main>` 位于子节点位置的表达式可以接受多种类型的值:
- 字符串、数字和布尔值等原始值。
- 使用
html函数(或者,如果表达式位于<svg>元素内部,则使用svg函数)创建的TemplateResult对象。 - 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 节点都可以传递给子表达式。通常应该通过使用 html 指定模板来渲染 DOM 节点,但在需要时也可以直接渲染 DOM 节点。节点会被附加到 DOM 树的该位置,因此会从当前父节点中移除:
const div = document.createElement('div');const page = html` ${div} <p>This is some text</p>`; 任何受支持类型的数组或可迭代对象
Permalink to "任何受支持类型的数组或可迭代对象"表达式还可以返回任何受支持类型的数组或可迭代对象,且可以任意组合。你可以将此功能与 Array map 方法等标准 JavaScript 一起使用,来创建重复模板和列表。示例请参阅列表。
移除子节点内容
Permalink to "移除子节点内容"值 null、undefined、空字符串 '' 以及 Lit 的 nothing 哨兵值会移除之前渲染的内容,并且不渲染任何节点。
设置或移除子节点内容通常基于条件来完成。更多信息请参阅有条件地渲染 nothing。
当表达式是具有 Shadow DOM 的元素的子节点,且该 Shadow DOM 包含带有后备内容的 slot 时,不渲染任何节点可能很重要。不渲染任何节点可确保后备内容被渲染。更多信息请参阅后备内容。
属性表达式
Permalink to "属性表达式"除了使用表达式添加子节点外,你还可以使用表达式来设置元素的属性和 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 属性。
设置或移除属性通常基于条件来完成。更多信息请参阅有条件地渲染 nothing。
Property 表达式
Permalink to "Property 表达式"你可以使用 . 前缀和属性名来设置元素的 JavaScript Property:
html`<input .value=${this.itemCount}>`; 上述代码的行为与直接在 input 元素上设置 value 属性相同,例如:
inputEl.value = this.itemCount; 你可以使用 Property 表达式语法将复杂数据向下传递给子组件。例如,如果你有一个具有 listItems Property 的 my-list 组件,你可以传递一个对象数组:
html`<my-list .listItems=${this.items}></my-list>`; 请注意,此示例中的属性名——listItems——是混合大小写的。虽然 HTML 属性不区分大小写,但 Lit 在处理模板时会保留属性名的大小写。
有关组件 Property 的更多信息,请参阅响应式属性。
事件监听表达式
Permalink to "事件监听表达式"模板还可以包含声明式事件监听器。使用 @ 前缀后跟事件名称。表达式应求值为一个事件监听器。
html`<button @click=${this.clickHandler}>Click Me!</button>`; 这类似于在按钮元素上调用 addEventListener('click', this.clickHandler)。
事件监听器可以是一个普通函数,也可以是一个具有 handleEvent 方法的对象——这与标准 addEventListener 方法的 listener 参数相同。
在 Lit 组件中,事件监听器会自动绑定到组件,因此你可以在处理函数中使用 this 来引用组件实例。
clickHandler() { this.clickCount++;} 有关组件事件的更多信息,请参阅事件。
元素表达式
Permalink to "元素表达式"你还可以添加访问元素实例的表达式,而不是元素上的单个 Property 或属性:
html`<div ${myDirective()}></div>` 元素表达式只能与指令配合使用。元素表达式中的任何其他值类型都会被忽略。
一个可以在元素表达式中使用的内置指令是 ref 指令。它提供了对已渲染元素的引用。
html`<button ${ref(this.myRef)}`; 更多信息请参阅 ref。
合规的 HTML
Permalink to "合规的 HTML"Lit 模板必须是合规的 HTML。模板在任何值被插值之前,会由浏览器内置的 HTML 解析器进行解析。请遵循以下规则以确保模板合规:
当所有表达式替换为空值时,模板必须是合规的 HTML。
模板可以有多个顶层元素和文本。
模板_不应包含_未关闭的元素——它们会被 HTML 解析器自动关闭。
// HTML 解析器会在 "Some text" 之后关闭这个 divconst template1 = html`<div class="broken-div">Some text`;// 拼接后,"more text" 不会在 .broken-div 中const template2 = html`${template1} more text. </div>`;
由于浏览器的内置解析器非常宽松,大多数畸形模板的情况在运行时是无法检测到的,因此你不会看到警告——只会看到模板行为不符合预期。我们建议在开发过程中使用 linting 工具和 IDE 插件来查找模板中的问题。
有效的表达式位置
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 内容来动态更新表达式,并会在开发模式下报错。<!-- 错误 --><template>${content}</template><!-- 正确 --><template id="${attrValue}">static content ok</template><textarea>元素内容内部(textarea 元素本身的属性表达式是允许的)。请注意,Lit 可以将内容渲染到 textarea 中,但是编辑 textarea 会破坏 Lit 用于动态更新的 DOM 引用,Lit 会在开发模式下发出警告。相反,应该绑定到 textarea 的.valueProperty。<!-- 注意 --><textarea>${content}</textarea><!-- 正确 --><textarea .value=${content}></textarea><!-- 正确 --><textarea id="${attrValue}">static content ok</textarea>类似地,具有
contenteditable属性的元素内容内部也是如此。相反,应该绑定到元素的.innerTextProperty。<!-- 注意 --><div contenteditable>${content}</div><!-- 正确 --><div contenteditable .innerText=${content}></div><!-- 正确 --><div contenteditable id="${attrValue}">static content ok</div>HTML 注释内部。Lit 不会更新注释中的表达式,表达式会以 Lit token 字符串的形式渲染。但是,这不会破坏后续表达式,因此在开发过程中注释掉可能包含表达式的 HTML 代码块是安全的。
<!-- 不会更新: ${value} -->使用 ShadyCSS polyfill 时的
<style>元素内部。详见表达式和 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 一节。
非 literal 的静态值
Permalink to "非 literal 的静态值"在少数情况下,你可能需要将静态 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(); 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(); return html` <${unsafeStatic(tag)} ${unsafeStatic(activeAttribute)}=${this.active}> <p>${this.caption}</p> </${unsafeStatic(tag)}>`; }}customElements.define('my-button', MyButton); 请注意,使用 unsafeStatic 的行为与 literal 具有相同的注意事项:由于值的更改会导致新的模板被解析和缓存在内存中,因此不应频繁更改。