内置指令
指令是可以通过自定义表达式渲染方式来扩展 Lit 的函数。Lit 内置了许多指令,帮助满足各种渲染需求:
| 指令 | 说明 |
|---|---|
样式 | |
根据对象为元素设置一组类名。 | |
根据对象为元素设置一组样式属性。 | |
循环和条件渲染 | |
| 根据条件渲染两个模板中的一个。 | |
| 根据键值渲染多个模板中的一个。 | |
| 使用函数转换可迭代对象。 | |
| 将可迭代对象的值渲染到 DOM 中,支持可选的键控以实现数据差异比对和 DOM 稳定性。 | |
| 将可迭代对象中的值与连接符值交替排列。 | |
| 创建一个数字序列的可迭代对象,适用于需要固定次数迭代的场景。 | |
| 当值已定义时设置属性,值未定义时移除属性。 | |
缓存和变更检测 | |
| 在切换模板时缓存已渲染的 DOM,而不是丢弃 DOM。 | |
| 将可渲染值与唯一键关联,当键改变时强制重新渲染 DOM。 | |
| 仅在其依赖项发生变化时才重新评估模板。 | |
| 当属性或特性值与实时 DOM 值(而非上次渲染的值)不同时才进行设置。 | |
引用渲染的 DOM | |
| 获取模板中渲染元素的引用。 | |
渲染特殊值 | |
渲染 | |
| 将字符串作为 HTML 而非文本进行渲染。 | |
| 将字符串作为 SVG 而非文本进行渲染。 | |
异步渲染 | |
| 在 promise 解析之前渲染占位内容。 | |
将 | |
将 | |
只打包你使用的部分。 这些指令被称为"内置"指令,是因为它们是 Lit 包的一部分。但每个指令都是独立的模块,因此你的应用只会打包你导入的指令。
你也可以构建自己的指令。更多信息请参阅自定义指令。
classMap
Permalink to "classMap"根据对象为元素设置一组类名。
| 导入 | |
| 签名 | |
| 可用位置 |
|
classMap 指令使用 element.classList API 根据用户传入的对象高效地为元素添加和移除类名。对象中的每个键被视为类名,如果键关联的值为真值,则该类名将被添加到元素上。在后续渲染中,之前设置的值为假值或已不在对象中的类名将被移除。
customElement('my-element')class MyElement extends LitElement {
property({type: Boolean}) enabled = false;
render() { const classes = { enabled: this.enabled, hidden: false }; return html`<div class=${classMap(classes)}>Classy text</div>`; }} class MyElement extends LitElement { static properties = { enabled: {type: Boolean}, };
constructor() { super(); this.enabled = false; }
render() { const classes = { enabled: this.enabled, hidden: false }; return html`<div class=${classMap(classes)}>Classy text</div>`; }}customElements.define('my-element', MyElement); classMap 必须是 class 属性中唯一的表达式,但可以与静态值组合使用:
html`<div class="my-widget ${classMap(dynamicClasses)}">Static and dynamic</div>`; 在 Playground 中探索更多关于 classMap 的用法。
styleMap
Permalink to "styleMap"根据对象为元素设置一组样式属性。
| 导入 | |
| 签名 | |
| 可用位置 |
|
styleMap 指令使用 element.style API 根据用户传入的对象高效地为元素添加和移除内联样式。对象中的每个键被视为样式属性名,值被视为该属性的值。在后续渲染中,之前设置的值为 undefined 或 null 的样式属性将被移除(设为 null)。
customElement('my-element')class MyElement extends LitElement {
property({type: Boolean}) enabled = false;
render() { const styles = { backgroundColor: this.enabled ? 'blue' : 'gray', color: 'white' }; return html`<p style=${styleMap(styles)}>Hello style!</p>`; }} class MyElement extends LitElement { static properties = { enabled: {type: Boolean}, };
constructor() { super(); this.enabled = false; }
render() { const styles = { backgroundColor: this.enabled ? 'blue' : 'gray', color: 'white' }; return html`<p style=${styleMap(styles)}>Hello style!</p>`; }}customElements.define('my-element', MyElement); 对于包含连字符的 CSS 属性,你可以使用驼峰命名,或者将属性名放在引号中。例如,CSS 属性 font-family 可以写成 fontFamily 或 'font-family':
{ fontFamily: 'roboto' }{ 'font-family': 'roboto' } 对于 CSS 自定义属性,如 --custom-color,需要将整个属性名放在引号中:
{ '--custom-color': 'steelblue' } styleMap 必须是 style 属性中唯一的表达式,但可以与静态值组合使用:
html`<p style="color: white; ${styleMap(moreStyles)}">More styles!</p>`; 在 Playground 中探索更多关于 styleMap 的用法。
循环和条件渲染
Permalink to "循环和条件渲染"根据条件渲染两个模板中的一个。
| 导入 | |
| 签名 | |
| 可用位置 | 任意 |
当 condition 为 true 时,返回调用 trueCase() 的结果;如果定义了 falseCase,则在条件为 false 时返回调用 falseCase() 的结果。
这是一个对三元表达式的便捷封装,使得在没有 else 分支时编写内联条件更加简洁。
class MyElement extends LitElement { render() { return html` ${when(this.user, () => html`User: ${this.user.username}`, () => html`Sign In...`)} `; }} choose
Permalink to "choose"根据给定 value 与 case 的匹配情况,从一组 case 中选择并执行对应的模板函数。
| 导入 | |
| 签名 | |
| 可用位置 | 任意 |
case 的结构为 [caseValue, func]。value 通过严格相等与 caseValue 匹配。选择第一个匹配的 case。case 值可以是任何类型,包括基本类型、对象和 symbol。
这类似于 switch 语句,但作为表达式使用且没有 fallthrough。
class MyElement extends LitElement { render() { return html` ${choose(this.section, [ ['home', () => html`<h1>Home</h1>`], ['about', () => html`<h1>About</h1>`] ], () => html`<h1>Error</h1>`)} `; }} 返回一个可迭代对象,包含对 items 中每个值调用 f(value) 的结果。
| 导入 | |
| 签名 | |
| 可用位置 | 任意 |
map() 是一个对 for/of 循环 的简单封装,使得在表达式中使用可迭代对象更加方便。map() 总是原地更新任何创建的 DOM——它不进行任何差异比对或 DOM 移动。如果你需要这些功能,请参阅 repeat。map() 比 repeat() 更小、更快,因此如果你不需要差异比对和 DOM 稳定性,请优先使用 map()。
class MyElement extends LitElement { render() { return html` <ul> ${map(items, (i) => html`<li>${i}</li>`)} </ul> `; }} repeat
Permalink to "repeat"将可迭代对象的值渲染到 DOM 中,支持可选的键控以实现数据差异比对和 DOM 稳定性。
| 导入 | |
| 签名 | |
| 可用位置 | 子表达式 |
重复一系列从可迭代对象生成的值(通常是 TemplateResult),并在可迭代对象变化时高效地更新这些项。当提供了 keyFn 时,通过在需要时移动生成的 DOM 来维护键与 DOM 之间的关联,这通常是使用 repeat 最高效的方式,因为它对插入和移除操作执行最少的不必要工作。
如果你不使用键函数,你应该考虑使用 map()。
customElement('my-element')class MyElement extends LitElement {
property() items: Array<{id: number, name: string}> = [];
render() { return html` <ul> ${repeat(this.items, (item) => item.id, (item, index) => html` <li>${index}: ${item.name}</li>`)} </ul> `; }} class MyElement extends LitElement { static properties = { items: {}, };
constructor() { super(); this.items = []; }
render() { return html` <ul> ${repeat(this.items, (item) => item.id, (item, index) => html` <li>${index}: ${item.name}</li>`)} </ul> `; }}customElements.define('my-element', MyElement); 如果未提供 keyFn,repeat 的行为将类似于简单的从 items 到 values 的映射,DOM 会被复用到可能不同的 items 上。
请参阅何时使用 map 或 repeat 以了解何时使用 repeat,何时使用标准 JavaScript 流程控制。
在 Playground 中探索更多关于 repeat 的用法。
返回一个可迭代对象,包含 items 中的值与 joiner 值交替排列的结果。
| 导入 | |
| 签名 | |
| 可用位置 | 任意 |
class MyElement extends LitElement {
render() { return html` ${join( map(menuItems, (i) => html`<a href=${i.href}>${i.label}</a>`), html`<span class="separator">|</span>` )} `; }}range
Permalink to "range"返回从 start 到 end(不包含)以 step 递增的整数可迭代对象。
| 导入 | |
| 签名 | |
| 可用位置 | 任意 |
class MyElement extends LitElement {
render() { return html` ${map(range(8), (i) => html`${i + 1}`)} `; }}ifDefined
Permalink to "ifDefined"当值已定义时设置属性,值未定义时移除属性。
| 导入 | |
| 签名 | |
| 可用位置 | 属性表达式 |
对于 AttributePart,当值已定义时设置属性,当值未定义(undefined 或 null)时移除属性。对于其他 part 类型,该指令不执行任何操作。
当单个属性值中存在多个表达式时,如果 任何 表达式使用了 ifDefined 且结果为 undefined/null,该属性将被移除。这对于设置 URL 属性特别有用——当 URL 所需的部分未定义时,属性不会被设置,从而防止出现 404 错误。
customElement('my-element')class MyElement extends LitElement {
property() filename: string | undefined = undefined;
property() size: string | undefined = undefined;
render() { // 如果 size 或 filename 中任一为 undefined,则不渲染 src 属性 return html`<img src="/images/${ifDefined(this.size)}/${ifDefined(this.filename)}">`; }} class MyElement extends LitElement { static properties = { filename: {}, size: {}, };
constructor() { super(); this.filename = undefined; this.size = undefined; }
render() { // 如果 size 或 filename 中任一为 undefined,则不渲染 src 属性 return html`<img src="/images/${ifDefined(this.size)}/${ifDefined(this.filename)}">`; }}customElements.define('my-element', MyEleent); 在 Playground 中探索更多关于 ifDefined 的用法。
缓存和变更检测
Permalink to "缓存和变更检测"cache
Permalink to "cache"在切换模板时缓存已渲染的 DOM,而不是丢弃 DOM。你可以在频繁切换大型模板时使用此指令来优化渲染性能。
| 导入 | |
| 签名 | |
| 可用位置 | 子表达式 |
当传递给 cache 的值在一个或多个 TemplateResult 之间切换时,给定模板的渲染 DOM 节点在不使用时会被缓存。当模板变化时,该指令会在切换到新值之前缓存 当前 DOM 节点,并在切换回之前渲染的值时从缓存中恢复,而不是重新创建 DOM 节点。
const detailView = (data) => html`<div>...</div>`;const summaryView = (data) => html`<div>...</div>`;
customElement('my-element')class MyElement extends LitElement {
property() data = {showDetails: true, /*...*/ };
render() { return html`${cache(this.data.showDetails ? detailView(this.data) : summaryView(this.data) )}`; }} const detailView = (data) => html`<div>...</div>`;const summaryView = (data) => html`<div>...</div>`;
class MyElement extends LitElement { static properties = { data: {}, };
constructor() { super(); this.data = {showDetails: true, /*...*/ }; }
render() { return html`${cache(this.data.showDetails ? detailView(this.data) : summaryView(this.data) )}`; }}customElements.define('my-element', MyElement); 当 Lit 重新渲染模板时,它只更新修改的部分:不会创建或移除超出需要的 DOM。但当你从一个模板切换到另一个模板时,Lit 会移除旧的 DOM 并渲染新的 DOM 树。
cache 指令为给定的表达式和输入模板缓存生成的 DOM。在上面的示例中,它缓存了 summaryView 和 detailView 两个模板的 DOM。当你从一个视图切换到另一个视图时,Lit 会交换进缓存的新视图版本,并用最新数据更新它。当这些视图被频繁切换时,这可以提高渲染性能。
在 Playground 中探索更多关于 cache 的用法。
keyed
Permalink to "keyed"将可渲染值与唯一键关联。当键改变时,之前渲染的 DOM 会在渲染下一个值之前被移除并销毁,即使值(如模板)是相同的。
| 导入 | |
| 签名 | |
| 可用位置 | 任意表达式 |
当你渲染有状态元素且需要确保在某些关键数据变化时清除元素的所有状态时,keyed 非常有用。它实质上是退出了 Lit 默认的 DOM 复用策略。
keyed 在某些动画场景中也很有用,如果你需要强制为"进入"或"退出"动画创建新元素。
customElement('my-element')class MyElement extends LitElement {
property() userId: string = '';
render() { return html` <div> ${keyed(this.userId, html`<user-card .userId=${this.userId}></user-card>`)} </div>`; }} class MyElement extends LitElement { static properties = { userId: {}, };
constructor() { super(); this.userId = ''; }
render() { return html` <div> ${keyed(this.userId, html`<user-card .userId=${this.userId}></user-card>`)} </div>`; }}customElements.define('my-element', MyElement); guard
Permalink to "guard"仅在其依赖项发生变化时才重新评估模板,通过防止不必要的工作来优化渲染性能。
| 导入 | |
| 签名 | |
| 可用位置 | 任意表达式 |
渲染 valueFn 返回的值,且仅当依赖项之一发生引用变化时才重新评估 valueFn。
其中:
dependencies是一个需要监控变化的值数组。valueFn是一个返回可渲染值的函数。
guard 对于不可变数据模式非常有用,它可以防止在数据更新之前执行昂贵的操作。
customElement('my-element')class MyElement extends LitElement {
property() value: string = '';
render() { return html` <div> ${guard([this.value], () => calculateSHA(this.value))} </div>`; }} class MyElement extends LitElement { static properties = { value: {}, };
constructor() { super(); this.value = ''; }
render() { return html` <div> ${guard([this.value], () => calculateSHA(this.value))} </div>`; }}customElements.define('my-element', MyElement); 在此示例中,昂贵的 calculateSHA 函数仅在 value 属性发生变化时运行。
在 Playground 中探索更多关于 guard 的用法。
当属性或特性值与实时 DOM 值(而非上次渲染的值)不同时才进行设置。
| 导入 | |
| 签名 | |
| 可用位置 | 属性或特性表达式 |
在确定是否更新值时,与 实时 DOM 值进行比较,而不是像 Lit 默认行为那样与上次设置的值进行比较。
这在 DOM 值可能从 Lit 外部改变的情况下很有用。例如,当使用表达式设置 <input> 元素的 value 属性、content editable 元素的文本,或设置一个会自行更改属性或特性的自定义元素时。
在这些情况下,如果 DOM 值改变了,但通过 Lit 表达式设置的值没有改变,Lit 将不知道需要更新 DOM 值,从而保持原样。如果这不是你想要的——如果你希望无论如何都用绑定的值覆盖 DOM 值——请使用 live() 指令。
customElement('my-element')class MyElement extends LitElement {
property() data = {value: 'test'};
render() { return html`<input .value=${live(this.data.value)}>`; }} class MyElement extends LitElement { static properties = { data: {}, };
constructor() { super(); this.data = {value: 'test'}; }
render() { return html`<input .value=${live(this.data.value)}>`; }}customElements.define('my-element', MyElement); live() 对实时 DOM 值进行严格相等检查,如果新值与实时值相等,则不执行任何操作。这意味着当表达式会导致类型转换时不应使用 live()。如果你在属性表达式中使用 live(),请确保只传入字符串,否则表达式将在每次渲染时都更新。
在 Playground 中探索更多关于 live 的用法。
渲染特殊值
Permalink to "渲染特殊值"templateContent
Permalink to "templateContent"渲染 <template> 元素的内容。
| 导入 | |
| 签名 | |
| 可用位置 | 子表达式 |
Lit 模板编码在 JavaScript 中,因此可以嵌入使模板动态化的 JavaScript 表达式。如果你有一个需要包含在 Lit 模板中的静态 HTML <template>,可以使用 templateContent 指令克隆模板内容并将其包含在你的 Lit 模板中。只要模板元素引用在渲染之间不发生变化,后续渲染将不会执行任何操作。
注意,模板内容应由开发者控制,不得使用不受信任的字符串创建。不受信任的内容示例包括查询字符串参数和用户输入的值。使用此指令渲染不受信任的模板可能导致跨站脚本攻击(XSS)漏洞。
const templateEl = document.querySelector('template#myContent') as HTMLTemplateElement;
customElement('my-element')class MyElement extends LitElement {
render() { return html` Here's some content from a template element: ${templateContent(templateEl)}`; }} const templateEl = document.querySelector('template#myContent');
class MyElement extends LitElement {
render() { return html` Here's some content from a template element: ${templateContent(templateEl)}`; }}customElements.define('my-element', MyElement); 在 Playground 中探索更多关于 templateContent 的用法。
unsafeHTML
Permalink to "unsafeHTML"将字符串作为 HTML 而非文本进行渲染。
| 导入 | |
| 签名 | |
| 可用位置 | 子表达式 |
Lit 模板语法的一个关键特性是,只有源自模板字面量的字符串才会被解析为 HTML。因为模板字面量只能在受信任的脚本文件中编写,这自然地防止了 XSS 攻击注入不受信任的 HTML。然而,在某些情况下,非源自脚本文件的 HTML 需要在 Lit 模板中渲染,例如从数据库获取的受信任 HTML 内容。unsafeHTML 指令会将此类字符串解析为 HTML 并在 Lit 模板中渲染。
注意,传递给 unsafeHTML 的字符串必须由开发者控制,不得包含不受信任的内容。不受信任的内容示例包括查询字符串参数和用户输入的值。使用此指令渲染不受信任的内容可能导致跨站脚本攻击(XSS)漏洞。
const markup = '<h3>Some HTML to render.</h3>';
customElement('my-element')class MyElement extends LitElement {
render() { return html` Look out, potentially unsafe HTML ahead: ${unsafeHTML(markup)} `; }} const markup = '<h3>Some HTML to render.</h3>';
class MyElement extends LitElement {
render() { return html` Look out, potentially unsafe HTML ahead: ${unsafeHTML(markup)} `; }}customElements.define('my-element', MyElement); 在 Playground 中探索更多关于 unsafeHTML 的用法。
unsafeSVG
Permalink to "unsafeSVG"将字符串作为 SVG 而非文本进行渲染。
| 导入 | |
| 签名 | |
| 可用位置 | 子表达式 |
与 unsafeHTML 类似,在某些情况下,非源自脚本文件的 SVG 内容需要在 Lit 模板中渲染,例如从数据库获取的受信任 SVG 内容。unsafeSVG 指令会将此类字符串解析为 SVG 并在 Lit 模板中渲染。
注意,传递给 unsafeSVG 的字符串必须由开发者控制,不得包含不受信任的内容。不受信任的内容示例包括查询字符串参数和用户输入的值。使用此指令渲染不受信任的内容可能导致跨站脚本攻击(XSS)漏洞。
const svg = '<circle cx="50" cy="50" r="40" fill="red" />';
customElement('my-element')class MyElement extends LitElement {
render() { return 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> `; }} const svg = '<circle cx="50" cy="50" r="40" fill="red" />';
class MyElement extends LitElement {
render() { return 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> `; }}customElements.define('my-element', MyElement); 在 Playground 中探索更多关于 unsafeSVG 的用法。
引用渲染的 DOM
Permalink to "引用渲染的 DOM"获取渲染到 DOM 中的元素的引用。
| 导入 | |
| 签名 | |
| 可用位置 | 元素表达式 |
虽然 Lit 中的大多数 DOM 操作都可以通过模板声明式地完成,但在高级场景中,可能需要获取模板中渲染元素的引用并进行命令式操作。常见的使用场景包括聚焦表单控件或对容器元素调用命令式 DOM 操作库。
当放置在模板中的元素上时,ref 指令会在元素渲染后获取对该元素的引用。元素引用可以通过两种方式获取:传递 Ref 对象或传递回调函数。
Ref 对象作为元素引用的容器,可以使用 ref 模块中的 createRef 辅助方法创建。渲染后,Ref 的 value 属性将被设置为该元素,可以在 updated 等渲染后的生命周期中访问。
customElement('my-element')class MyElement extends LitElement {
inputRef: Ref<HTMLInputElement> = createRef();
render() { // 将 ref 指令传递给一个 Ref 对象,该对象将在 .value 中持有元素 return html`<input ${ref(this.inputRef)}>`; }
firstUpdated() { const input = this.inputRef.value!; input.focus(); }} class MyElement extends LitElement {
inputRef = createRef();
render() { // 将 ref 指令传递给一个 Ref 对象,该对象将在 .value 中持有元素 return html`<input ${ref(this.inputRef)}>`; }
firstUpdated() { const input = this.inputRef.value!; input.focus(); }}customElements.define('my-element', MyElement); 也可以向 ref 指令传递一个 ref 回调函数。当被引用的元素发生变化时,回调将被调用。如果 ref 回调在后续渲染中被渲染到不同的元素位置或被移除,它将先以 undefined 作为参数被调用,然后(如果有的话)以新渲染到的元素再次被调用。注意在 LitElement 中,回调将自动绑定到宿主元素。
customElement('my-element')class MyElement extends LitElement {
render() { // 向 ref 指令传递一个变更回调 return html`<input ${ref(this.inputChanged)}>`; }
inputChanged(input?: HTMLInputElement) { input?.focus(); }} class MyElement extends LitElement {
render() { // 向 ref 指令传递一个变更回调 return html`<input ${ref(this.inputChanged)}>`; }
inputChanged(input) { input?.focus(); }}customElements.define('my-element', MyElement); 在 Playground 中探索更多关于 ref 的用法。
until
Permalink to "until"在 promise 解析之前渲染占位内容。
| 导入 | |
| 签名 | |
| 可用位置 | 任意表达式 |
接受一系列值,包括 Promise。值按优先级顺序渲染,第一个参数具有最高优先级,最后一个参数具有最低优先级。如果值是一个 Promise,在它解析之前将渲染优先级较低的值。
值的优先级可用于为异步数据创建占位内容。例如,带有待加载内容的 Promise 可以作为第一个(最高优先级)参数,非 Promise 的加载指示模板可以作为第二个(较低优先级)参数。加载指示器会立即渲染,主要内容将在 Promise 解析后渲染。
customElement('my-element')class MyElement extends LitElement {
state() private content = fetch('./content.txt').then(r => r.text());
render() { return html`${until(this.content, html`<span>Loading...</span>`)}`; }} class MyElement extends LitElement { static properties = { content: {state: true}, };
constructor() { super(); this.content = fetch('./content.txt').then(r => r.text()); }
render() { return html`${until(this.content, html`<span>Loading...</span>`)}`; }}customElements.define('my-element', MyElement); 在 Playground 中探索更多关于 until 的用法。
asyncAppend
Permalink to "asyncAppend"将 AsyncIterable 产生的值追加到 DOM 中。
| 导入 | |
| 签名 | |
| 可用位置 | 子表达式 |
asyncAppend 渲染异步可迭代对象的值,将每个新值追加在前一个值之后。注意异步生成器也实现了异步可迭代协议,因此可以被 asyncAppend 消费。
async function *tossCoins(count: number) { for (let i=0; i<count; i++) { yield Math.random() > 0.5 ? 'Heads' : 'Tails'; await new Promise((r) => setTimeout(r, 1000)); }}
customElement('my-element')class MyElement extends LitElement {
state() private tosses = tossCoins(10);
render() { return html` <ul>${asyncAppend(this.tosses, (v: string) => html`<li>${v}</li>`)}</ul>`; }} async function *tossCoins(count) { for (let i=0; i<count; i++) { yield Math.random() > 0.5 ? 'Heads' : 'Tails'; await new Promise((r) => setTimeout(r, 1000)); }}
class MyElement extends LitElement { static properties = { tosses: {state: true}, };
constructor() { super(); this.tosses = tossCoins(10); }
render() { return html` <ul>${asyncAppend(this.tosses, (v) => html`<li>${v}</li>`)}</ul>`; }}customElements.define('my-element', MyElement); 在 Playground 中探索更多关于 asyncAppend 的用法。
asyncReplace
Permalink to "asyncReplace"将 AsyncIterable 产生的最新值渲染到 DOM 中。
| 导入 | |
| 签名 | |
| 可用位置 | 任意表达式 |
与 asyncAppend 类似,asyncReplace 渲染异步可迭代对象的值,用每个新值替换前一个值。
async function *countDown(count: number) { while (count > 0) { yield count--; await new Promise((r) => setTimeout(r, 1000)); }}
customElement('my-element')class MyElement extends LitElement {
state() private timer = countDown(10);
render() { return html`Timer: <span>${asyncReplace(this.timer)}</span>.`; }} async function *countDown(count) { while (count > 0) { yield count--; await new Promise((r) => setTimeout(r, 1000)); }}
class MyElement extends LitElement { static properties = { timer: {state: true}, };
constructor() { super(); this.timer = countDown(10); }
render() { return html`Timer: <span>${asyncReplace(this.timer)}</span>.`; }}customElements.define('my-element', MyElement); 在 Playground 中探索更多关于 asyncReplace 的用法。