`,表示一个*组件*。
```svelte
```
## 元素属性
默认情况下,属性的工作方式与其 HTML 对应项完全相同。
```svelte
```
与 HTML 一样,属性值可以不加引号。
```svelte
```
属性值可以包含 JavaScript 表达式。
```svelte
page {p}
```
或者它们本身就可以*是* JavaScript 表达式。
```svelte
```
如果布尔属性的值为[真值](https://developer.mozilla.org/en-US/docs/Glossary/Truthy),则会包含在元素中,如果为[假值](https://developer.mozilla.org/en-US/docs/Glossary/Falsy),则会被排除。
所有其他属性除非其值为[空值](https://developer.mozilla.org/en-US/docs/Glossary/Nullish)(`null` 或 `undefined`),否则都会被包含。
```svelte
This div has no title attribute
```
> [!NOTE] 在单个表达式外加引号不会影响值的解析方式,但在 Svelte 6 中会导致值被强制转换为字符串:
>
>
> ```svelte
>
> ```
当属性名和值相同时(`name={name}`),可以简写为 `{name}`。
```svelte
```
## 组件属性
按照惯例,传递给组件的值被称为*属性(properties)*或*props*,而不是*特性(attributes)*,后者是 DOM 的一个特征。
> 译者注:Property 和 Attributes 通常都翻译为“属性”,在做区分时,通常会将 Attributes 翻译为“特性”。Attributes 指的是直接在 HTML 元素上设置的值,通过提供元素的附加信息来指导其初始行为和状态。
与元素一样,`name={name}` 可以简写为 `{name}`。
```svelte
```
*展开属性*允许一次性将多个特性或属性传递给元素或组件。
一个元素或组件可以有多个展开属性,并与常规属性交错使用。
```svelte
```
## 事件
通过在元素上添加以 `on` 开头的属性,可以监听 DOM 事件。例如,要监听 `click` 事件,在按钮上添加 `onclick` 属性:
```svelte
```
事件属性区分大小写。`onclick` 监听 `click` 事件,`onClick` 监听 `Click` 事件,这是不同的。这确保你可以监听包含大写字符的自定义事件。
因为事件只是属性,所以适用与属性相同的规则:
- 你可以使用简写形式:``
- 你可以展开它们:``
在时序上,事件属性总是在绑定事件之后触发(例如,`oninput` 总是在 `bind:value` 更新后触发)。在底层,一些事件处理程序是直接通过 `addEventListener` 附加的,而其他的则是*委托*的。
当使用 `ontouchstart` 和 `ontouchmove` 事件属性时,处理程序是[passive](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners)可以获得更好的性能。这极大地提高了响应性,因为浏览器可以立即滚动文档,而不是等待查看事件处理程序是否调用 `event.preventDefault()`。
在极少数情况下,如果你需要阻止这些事件的默认行为,你应该使用 [`on`](svelte-events#on)(例如在 action 内部)。
### 事件委托
为了减少内存占用并提高性能,Svelte 使用了一种称为事件委托的技术。这意味着对于某些事件(见下面的列表),在应用程序根部的单个事件监听器负责运行事件路径上的所有处理程序。
需要注意以下几个陷阱:
- 当你手动触发一个带有委托监听器的事件时,确保设置 `{ bubbles: true }` 选项,否则它将无法到达应用程序根部
- 当直接使用 `addEventListener` 时,避免调用 `stopPropagation`,否则事件将无法到达应用程序根部,处理程序将不会被调用。同样,在应用程序根部手动添加的处理程序将在 DOM 深处声明式添加的处理程序(例如用 `onclick={...}`)之前运行,无论是在捕获还是冒泡阶段。出于这些原因,最好使用从 `svelte/events` 导入的 `on` 函数,而不是 `addEventListener`,因为它将确保顺序得到保持,并且正确处理 `stopPropagation`。
以下事件处理程序是委托的:
- `beforeinput`
- `click`
- `change`
- `dblclick`
- `contextmenu`
- `focusin`
- `focusout`
- `input`
- `keydown`
- `keyup`
- `mousedown`
- `mousemove`
- `mouseout`
- `mouseover`
- `mouseup`
- `pointerdown`
- `pointermove`
- `pointerout`
- `pointerover`
- `pointerup`
- `touchend`
- `touchmove`
- `touchstart`
## 文本表达式
可以通过将 JavaScript 表达式用大括号括起来将其作为文本包含。
```svelte
{expression}
```
可以通过使用它们的 [HTML 实体](https://developer.mozilla.org/docs/Glossary/Entity)字符串在 Svelte 模板中包含大括号:`{`、`{` 或 `{` 表示 `{`,`}`、`}` 或 `}` 表示 `}`。
如果你使用正则表达式(`RegExp`)[字面量表示法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor),你需要用括号将其括起来。
```svelte
Hello {name}!
{a} + {b} = {a + b}.
{(/^[A-Za-z ]+$/).test(value) ? x : y}
```
表达式将被字符串化并转义以防止代码注入。如果你想渲染 HTML,请使用 `{@html}` 标签。
```svelte
{@html potentiallyUnsafeHtmlString}
```
> [!NOTE] 确保你要么对传入的字符串进行转义,要么只使用你控制下的值来填充它,以防止 [XSS 攻击](https://owasp.org/www-community/attacks/xss/)
## 注释
你可以在组件内使用 HTML 注释。
```svelte
Hello world
```
以 `svelte-ignore` 开头的注释会禁用下一个标记块的警告。通常,这些是可访问性警告;确保你有充分的理由禁用它们。
```svelte
```
你可以添加一个以 `@component` 开头的特殊注释,当在其他文件中悬停在组件名称上时会显示该注释。
````svelte
Hello, {name}
````
# {#if ...}
```svelte
{#if expression}...{/if}
```
```svelte
{#if expression}...{:else if expression}...{/if}
```
```svelte
{#if expression}...{:else}...{/if}
```
需要条件渲染的内容可以包装在 if 块中。
```svelte
{#if answer === 42}
问题是什么?
{/if}
```
可以使用 `{:else if expression}` 添加额外的条件,最后可以选择性地以 `{:else}` 子句结束。
```svelte
{#if porridge.temperature > 100}
太热了!
{:else if 80 > porridge.temperature}
太冷了!
{:else}
刚刚好!
{/if}
```
(块不必只包裹元素,它们也可以包裹元素内的文本。)
# {#each ...}
```svelte
{#each expression as name}...{/each}
```
```svelte
{#each expression as name, index}...{/each}
```
遍历值可以通过 each 块来完成。这些值可以是数组、类数组对象(即任何具有 `length` 属性的对象),或者可迭代对象如 `Map` 和 `Set` —— 换句话说,任何可以用 `Array.from` 处理的对象都可以。
```svelte
购物清单
{#each items as item}
- {item.name} x {item.qty}
{/each}
```
each 块还可以指定一个 _index_,相当于 `array.map(...)` 回调中的第二个参数:
```svelte
{#each items as item, i}
{i + 1}: {item.name} x {item.qty}
{/each}
```
## 带键的 each 块
```svelte
{#each expression as name (key)}...{/each}
```
```svelte
{#each expression as name, index (key)}...{/each}
```
如果提供了一个 _key_ 表达式(必须能唯一标识每个列表项),当数据发生变化时,Svelte 将使用它对列表进行差异比较,而不是在末尾添加或删除条目。key 可以是任何对象,但建议使用字符串和数字,因为它们允许在对象本身发生变化时保持标识继续存在。
```svelte
{#each items as item (item.id)}
{item.name} x {item.qty}
{/each}
{#each items as item, i (item.id)}
{i + 1}: {item.name} x {item.qty}
{/each}
```
你可以在 each 块中自由使用解构和剩余模式。
```svelte
{#each items as { id, name, qty }, i (id)}
{i + 1}: {name} x {qty}
{/each}
{#each objects as { id, ...rest }}
{id}
{/each}
{#each items as [id, ...rest]}
{id}
{/each}
```
## 不带条目的 each 块
```svelte
{#each expression}...{/each}
```
```svelte
{#each expression, index}...{/each}
```
如果你只是想渲染某些内容 `n` 次,可以省略 `as` 部分([demo](/playground/untitled#H4sIAAAAAAAAE3WR0W7CMAxFf8XKNAk0WsSeUEaRpn3Guoc0MbQiJFHiMlDVf18SOrZJ48259_jaVgZmxBEZZ28thgCNFV6xBdt1GgPj7wOji0t2EqI-wa_OleGEmpLWiID_6dIaQkMxhm1UdwKpRQhVzWSaVORJNdvWpqbhAYVsYQCNZk8thzWMC_DCHMZk3wPSThNQ088I3mghD9UwSwHwlLE5PMIzVFUFq3G7WUZ2OyUvU3JOuZU332wCXTRmtPy1NgzXZtUFp8WFw9536uWqpbIgPEaDsJBW90cTOHh0KGi2XsBq5-cT6-3nPauxXqHnsHJnCFZ3CvJVkyuCQ0mFF9TZyCQ162WGvteLKfG197Y3iv_pz_fmS68Hxt8iPBPj5HscP8YvCNX7uhYCAAA=)):
```svelte
{#each { length: 8 }, rank}
{#each { length: 8 }, file}
{/each}
{/each}
```
## Else 块
```svelte
{#each expression as name}...{:else}...{/each}
```
each 块还可以有一个 `{:else}` 子句,当列表为空时会渲染该子句。
```svelte
{#each todos as todo}
{todo.text}
{:else}
今天没有任务!
{/each}
```
# {#key ...}
```svelte
{#key expression}...{/key}
```
当表达式的值发生变化时,Key 块会销毁并重新创建其内容。当用在组件时,这将导致组件被重新实例化和重新初始化:
```svelte
{#key value}
{/key}
```
如果你想要在值发生变化时播放过渡效果,这也很有用:
```svelte
{#key value}
{value}
{/key}
```
# {#await ...}
```svelte
{#await expression}...{:then name}...{:catch name}...{/await}
```
```svelte
{#await expression}...{:then name}...{/await}
```
```svelte
{#await expression then name}...{/await}
```
```svelte
{#await expression catch name}...{/await}
```
Await 块允许你根据 [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 的三种可能状态 — 等待中(pending)、已完成(fulfilled)或已拒绝(rejected) — 进行分支处理。
```svelte
{#await promise}
等待 promise 解决中...
{:then value}
值是 {value}
{:catch error}
出现错误: {error.message}
{/await}
```
> [!NOTE] 在服务端渲染期间,只会渲染等待中分支。
>
> 如果提供的表达式不是一个 `Promise`,则只会渲染 `:then` 分支,包括在服务端渲染期间。
如果你不需要在 promise 被拒绝时渲染任何内容(或不可能出现错误),可以省略 `catch` 块。
```svelte
{#await promise}
等待 promise 解决中...
{:then value}
值是 {value}
{/await}
```
如果你不关心等待状态,你也可以省略初始块。
```svelte
{#await promise then value}
值是 {value}
{/await}
```
同样,如果你只想显示错误状态,你可以省略 `then` 块。
```svelte
{#await promise catch error}
错误是 {error}
{/await}
```
> [!NOTE] 你可以将 `#await` 和 [`import(...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) 一起使用实现组件懒加载:
>
> ```svelte
> {#await import('./Component.svelte') then { default: Component }}
>
> {/await}
> ```
# {#snippet ...}
```svelte
{#snippet name()}...{/snippet}
```
```svelte
{#snippet name(param1, param2, paramN)}...{/snippet}
```
代码片段和 [渲染标签](@render) 是在组件内部创建可复用标记块的一种方法。与其编写[这样的](/playground/untitled#H4sIAAAAAAAAE5VUYW-kIBD9K8Tmsm2yXXRzvQ-s3eR-R-0HqqOQKhAZb9sz_vdDkV1t000vRmHewMx7w2AflbIGG7GnPlK8gYhFv42JthG-m9Gwf6BGcLbVXZuPSGrzVho8ZirDGpDIhldgySN5GpEMez9kaNuckY1ANJZRamRuu2ZnhEZt6a84pvs43mzD4pMsUDDi8DMkQFYCGdkvsJwblFq5uCik9bmJ4JZwUkv1eoknWigX2eGNN6aGXa6bjV8ybP-X7sM36T58SVcrIIV2xVIaA41xeD5kKqWXuqpUJEefOqVuOkL9DfBchGrzWfu0vb-RpTd3o-zBR045Ga3HfuE5BmJpKauuhbPtENlUF2sqR9jqpsPSxWsMrlngyj3VJiyYjJXb1-lMa7IWC-iSk2M5Zzh-SJjShe-siq5kpZRPs55BbSGU5YPyte4vVV_VfFXxVb10dSLf17pS2lM5HnpPxw4Zpv6x-F57p0jI3OKlVnhv5V9wPQrNYQQ9D_f6aGHlC89fq1Z3qmDkJCTCweOGF4VUFSPJvD_DhreVdA0eu8ehJJ5x91dBaBkpWm3ureCFPt3uzRv56d4kdp-2euG38XZ6dsnd3ZmPG9yRBCrzRUvi-MccOdwz3qE-fOZ7AwAhlrtTUx3c76vRhSwlFBHDtoPhefgHX3dM0PkEAAA=)重复代码...
```svelte
{#each images as image}
{#if image.href}
{:else}
{/if}
{/each}
```
...你可以[这样写](/playground/untitled#H4sIAAAAAAAAE5VUYW-bMBD9KxbRlERKY4jWfSA02n5H6QcXDmwVbMs-lnaI_z6D7TTt1moTAnPvzvfenQ_GpBEd2CS_HxPJekjy5IfWyS7BFz0b9id0CM62ajDVjBS2MkLjqZQldoBE9KwFS-7I_YyUOPqlRGuqnKw5orY5pVpUduj3mitUln5LU3pI0_UuBp9FjTwnDr9AHETLMSeHK6xiGoWSLi9yYT034cwSRjohn17zcQPNFTs8s153sK9Uv_Yh0-5_5d7-o9zbD-UqCaRWrllSYZQxLw_HUhb0ta-y4NnJUxfUvc7QuLJSaO0a3oh2MLBZat8u-wsPnXzKQvTtVVF34xK5d69ThFmHEQ4SpzeVRediTG8rjD5vBSeN3E5JyHh6R1DQK9-iml5kjzQUN_lSgVU8DhYLx7wwjSvRkMDvTjiwF4zM1kXZ7DlF1eN3A7IG85e-zRrYEjjm0FkI4Cc7Ripm0pHOChexhcWXzreeZyRMU6Mk3ljxC9w4QH-cQZ_b3T5pjHxk1VNr1CDrnJy5QDh6XLO6FrLNSRb2l9gz0wo3S6m7HErSgLsPGMHkpDZK31jOanXeHPQz-eruLHUP0z6yTbpbrn223V70uMXNSpQSZjpL0y8hcxxpNqA6_ql3BQAxlxvfpQ_uT9GrWjQC6iRHM8D0MP0GQsIi92QEAAA=):
```svelte
{#snippet figure(image)}
{/snippet}
{#each images as image}
{#if image.href}
{@render figure(image)}
{:else}
{@render figure(image)}
{/if}
{/each}
```
像函数声明一样,代码片段可以有任意数量的参数,这些参数可以有默认值,并且你可以对每个参数进行解构。然而,你不能使用剩余参数。
## 代码片段作用域
代码片段可以在组件的任何地方声明。它们可以引用在自身之外声明的值,例如在 `
{#snippet hello(name)}
你好 {name}! {message}!
{/snippet}
{@render hello('alice')}
{@render hello('bob')}
```
...并且它们对同一词法作用域中的所有内容都是"可见的"(即兄弟节点和这些兄弟节点的子节点):
```svelte
{#snippet x()}
{#snippet y()}...{/snippet}
{@render y()}
{/snippet}
{@render y()}
{@render x()}
```
代码片段可以引用自身和其他片段 ([demo](/playground/untitled#H4sIAAAAAAAAE2WPTQqDMBCFrxLiRqH1Zysi7TlqF1YnENBJSGJLCYGeo5tesUeosfYH3c2bee_jjaWMd6BpfrAU6x5oTvdS0g01V-mFPkNnYNRaDKrxGxto5FKCIaeu1kYwFkauwsoUWtZYPh_3W5FMY4U2mb3egL9kIwY0rbhgiO-sDTgjSEqSTvIDs-jiOP7i_MHuFGAL6p9BtiSbOTl0GtzCuihqE87cqtyam6WRGz_vRcsZh5bmRg3gju4Fptq_kzQBAAA=)):
```svelte
{#snippet blastoff()}
🚀
{/snippet}
{#snippet countdown(n)}
{#if n > 0}
{n}...
{@render countdown(n - 1)}
{:else}
{@render blastoff()}
{/if}
{/snippet}
{@render countdown(10)}
```
## 将代码片段传递给组件
在模板中,代码片段和其他值一样。这样,它们可以作为 props 传递给组件 ([demo](/playground/untitled#H4sIAAAAAAAAE3VS247aMBD9lZGpBGwDASRegonaPvQL2qdlH5zYEKvBNvbQLbL875VzAcKyj3PmzJnLGU8UOwqSkd8KJdaCk4TsZS0cyV49wYuJuQiQpGd-N2bu_ooaI1YwJ57hpVYoFDqSEepKKw3mO7VDeTTaIvxiRS1gb_URxvO0ibrS8WanIrHUyiHs7Vmigy28RmyHHmKvDMbMmFq4cQInvGSwTsBYWYoMVhCSB2rBFFPsyl0uruTlR3JZCWvlTXl1Yy_mawiR_rbZKZrellJ-5JQ0RiBUgnFhJ9OGR7HKmwVoilXeIye8DOJGfYCgRlZ3iE876TBsZPX7hPdteO75PC4QaIo8vwNPePmANQ2fMeEFHrLD7rR1jTNkW986E8C3KwfwVr8HSHOSEBT_kGRozyIkn_zQveXDL3rIfPJHtUDwzShJd_Qk3gQCbOGLsdq4yfTRJopRuin3I7nv6kL7ARRjmLdBDG3uv1mhuLA3V2mKtqNEf_oCn8p9aN-WYqH5peP4kWBl1UwJzAEPT9U7K--0fRrrWnPTXpCm1_EVdXjpNmlA8G1hPPyM1fKgMqjFHjctXGjLhZ05w0qpDhksGrybuNEHtJnCalZWsuaTlfq6nPaaBSv_HKw-K57BjzOiVj9ZKQYKzQjZodYFqydYTRN4gPhVzTDO2xnma3HsVWjaLjT8nbfwHy7Q5f2dBAAA)):
```svelte
{#snippet header()}
水果 |
数量 |
价格 |
总计 |
{/snippet}
{#snippet row(d)}
{d.name} |
{d.qty} |
{d.price} |
{d.qty * d.price} |
{/snippet}
```
把它想象成向组件传递内容而非数据。这个概念类似于 Web 组件中的插槽。
为了方便,直接在组件*内部*声明的代码片段会隐式成为组件的 props ([demo](/playground/untitled#H4sIAAAAAAAAE3VSTa_aMBD8Kyu_SkAbCA-JSzBR20N_QXt6vIMTO8SqsY29tI2s_PcqTiB8vaPHs7MzuxuIZgdBMvJLo0QlOElIJZXwJHsLBBvb_XUASc7Mb9Yu_B-hsMMK5sUzvDQahUZPMkJ96aTFfKd3KA_WOISfrFACKmcOMFmk8TWUTjY73RFLoz1C5U4SPWzhrcN2GKDrlcGEWauEnyRwxCaDdQLWyVJksII2uaMWTDPNLtzX5YX8-kgua-GcHJVXI3u5WEPb0d83O03TMZSmfRzOkG1Db7mNacOL19JagVALxoWbztq-H8U6j0SaYp2P2BGbOyQ2v8PQIFMXLKRDk177pq0zf6d8bMrzwBdd0pamyPMb-IjNEzS2f86Gz_Dwf-2F9nvNSUJQ_EOSoTuJNvngqK5v4Pas7n4-OCwlEEJcQTIMO-nSQwtb-GSdsX46e9gbRoP9yGQ11I0rEuycunu6PHx1QnPhxm3SFN15MOlYEFJZtf0dUywMbwZOeBGsrKNLYB54-1R9WNqVdki7usim6VmQphf7mnpshiQRhNAXdoOfMyX3OgMlKtz0cGEcF27uLSul3mewjPjgOOoDukxjPS9rqfh0pb-8zs6aBSt_7505aZ7B9xOi0T9YKW4UooVsr0zB1BTrWQJ3EL-oWcZ572GxFoezCk37QLe3897-B2i2U62uBAAA)):
```svelte
{#snippet header()}
水果 |
数量 |
价格 |
总计 |
{/snippet}
{#snippet row(d)}
{d.name} |
{d.qty} |
{d.price} |
{d.qty * d.price} |
{/snippet}
```
组件标签内的任何*不是*代码片段声明的内容都将隐式成为 `children` 代码片段的一部分 ([demo](/playground/untitled#H4sIAAAAAAAAE3WOQQrCMBBFrzIMggql3ddY1Du4si5sOmIwnYRkFKX07lKqglqX8_7_w2uRDw1hjlsWI5ZqTPBoLEXMdy3K3fdZDzB5Ndfep_FKVnpWHSKNce1YiCVijirqYLwUJQOYxrsgsLmIOIZjcA1M02w4n-PpomSVvTclqyEutDX6DA2pZ7_ABIVugrmEC3XJH92P55_G39GodCmWBFrQJ2PrQAwdLGHig_NxNv9xrQa1dhWIawrv1Wzeqawa8953D-8QOmaEAQAA)):
```svelte
```
```svelte
```
> [!NOTE] 请注意,如果组件内部还有内容,你不能有名为 `children` 的 prop — 基于这个原因,应该避免使用这个名称作为 prop
你可以将代码片段 props 声明为可选的。你可以使用可选链,当代码片段未设置时不渲染任何内容...
```svelte
{@render children?.()}
```
...或者使用 `#if` 块来渲染后备内容:
```svelte
{#if children}
{@render children()}
{:else}
后备内容
{/if}
```
## 代码片段类型
代码片段实现了从 `'svelte'` 导入的 `Snippet` 接口:
```svelte
```
通过这项改动,如果你尝试在没有提供 `data` prop 和 `row` 代码片段的情况下使用该组件,则会出现红色波浪线。请注意,提供给 `Snippet` 的类型参数是一个[元组](https://ts.yayujs.com/handbook/ObjectTypes.html#%E5%85%83%E7%BB%84%E7%B1%BB%E5%9E%8B-tuple-types),因为代码片段可以有多个参数。
我们可以通过声明泛型来进一步收窄类型,以便 `data` 和 `row` 引用相同的类型:
```svelte
```
## 导出代码片段
在 `.svelte` 文件顶层声明的代码片段可以从 `
{#snippet add(a, b)}
{a} + {b} = {a + b}
{/snippet}
```
> [!NOTE]
> 这需要 Svelte 5.5.0 或更新版本
## 程序化代码片段
代码片段可以通过 [`createRawSnippet`](svelte#createRawSnippet) API 以编程方式创建。这适用于高级用例。
## 代码片段和插槽
在 Svelte 4 中,可以使用 [插槽](legacy-slots) 将内容传递给组件。代码片段更强大、更灵活,因此在 Svelte 5 中插槽已被弃用。
# {@render ...}
要渲染一个[代码片段](snippet),请使用 `{@render ...}` 标签。
```svelte
{#snippet sum(a, b)}
{a} + {b} = {a + b}
{/snippet}
{@render sum(1, 2)}
{@render sum(3, 4)}
{@render sum(5, 6)}
```
表达式可以是像 `sum` 这样的标识符,也可以是任意的 JavaScript 表达式:
```svelte
{@render (cool ? coolSnippet : lameSnippet)()}
```
## 可选代码片段
如果代码片段可能未定义 — 例如,因为它是一个传入的 prop — 那么你可以使用可选链操作符,只在代码片段确实存在时才渲染它:
```svelte
{@render children?.()}
```
或者,使用[`{#if ...}`](if) 块配合 `:else` 子句来渲染后备内容:
```svelte
{#if children}
{@render children()}
{:else}
后备内容
{/if}
```
# {@html ...}
要将原始 HTML 注入到组件中,请使用 `{@html ...}` 标签:
```svelte
{@html content}
```
> [!NOTE] 请确保您要么转义传入的字符串,要么仅使用在您控制下的值来填充它,以防止 [XSS 攻击](https://owasp.org/www-community/attacks/xss/)。切勿渲染未经安全处理的内容。
表达式应该是有效的独立 HTML —— 以下示例将不起作用,因为 `