首页
/ Svelte 模板基础语法详解:从 HTML++ 标记、属性规则到事件委托实现

Svelte 模板基础语法详解:从 HTML++ 标记、属性规则到事件委托实现

2026-09-05 12:20:28作者:齐添朝

本文基于 Svelte 官方文档 Basic markup 展开,系统讲解 Svelte 组件中"HTML++"模板体系的六大核心能力:元素与组件标签的区分规则、静态/动态属性及布尔属性的取值语义、组件 props 与展开属性、on* 事件属性及其底层的事件委托机制、花括号文本表达式与 HTML 转义,以及注释的多种形态。读完后,你将不仅会写 Svelte 模板,还能结合仓库源码(如 utils.jsevents.js)理解这些语法在编译期与运行期的真实行为。

Svelte 模板:HTML++ 心智模型

Svelte 组件中的标记(markup)可以被视为 HTML++:它在标准 HTML 之上叠加了 JavaScript 表达式、组件引用、展开属性和响应式能力,但不改变 HTML 的基本形态。掌握这套模板语法的关键在于分清三类标记——普通元素、组件、表达式——以及它们各自遵循的规则。

标签:元素还是组件

标签的大小写和命名方式决定了它的语义:

  • 小写标签(如 <div>)表示普通的 HTML 元素;
  • 首字母大写的标签(如 <Widget>)或使用点号分隔的标签(如 <my.stuff>)表示一个 组件(component)。
<script>
	import Widget from './Widget.svelte';
</script>

<div>
	<Widget />
</div>

组件必须先在 <script> 中通过 import 引入才能在模板中使用。编译器会根据标签名判断目标类型,进而走不同的转换路径——元素走 DOM 属性/事件逻辑,组件走 props 传参与实例化逻辑。

元素属性(Element Attributes)

静态属性与无引号值

默认情况下,元素属性与标准 HTML 行为完全一致:

<div class="foo">
	<button disabled>can't touch this</button>
</div>

和 HTML 一样,属性值可以不加引号:

<input type=checkbox />

表达式:在值中,或就是值

属性值中可以用花括号内嵌任意 JavaScript 表达式:

<a href="page/{p}">page {p}</a>

整个属性值也可以直接 一个表达式:

<button disabled={!clickable}>...</button>

这两种写法在 客户端属性编译逻辑 中都会被统一处理:静态字符串走字符串字面量路径,含表达式的属性则被编译为可在响应式更新时重新求值的动态调用。

布尔属性与 nullish 规则

这是 Svelte 属性语义中最容易被误解的一点,文档明确区分了两类规则:

  • 布尔属性:值为 truthy 时属性出现在元素上,值为 falsy 时属性被移除;
  • 其他所有属性:只要值不是 nullish(即 nullundefined)就会出现。
<input required={false} placeholder="This input field is not required" />
<div title={null}>This div has no title attribute</div>

[!NOTE] 给单个表达式加引号不影响值的解析方式,但在 Svelte 6 中会导致值被强制转换为字符串:

<button disabled="{number !== 42}">...</button>

哪些属性算"布尔属性"由源码中的白名单决定。在 utils.js 中,DOM_BOOLEAN_ATTRIBUTES 列出了 allowfullscreenautofocuscheckeddisabledmutedrequiredreadonly 等 28 个属性,is_boolean_attribute() 据此判断——所以 <button disabled={!clickable}> 中的 !clickable 走的是"truthy 则保留、falsy 则移除"的分支,而不是把 "false" 写进 DOM。

属性简写(Shorthand)

当属性名与值相同(name={name})时,可简写为 {name}

<button {disabled}>...</button>
<!-- 等价于
<button disabled={disabled}>...</button>
-->

组件 Props

按惯例,传给组件的值称为 properties / props,而 attributes 是 DOM 的概念。规则与元素属性一致,同样支持 {name} 简写:

<Widget foo={bar} answer={42} text="hello" />

组件内部如何接收这些值(传统 export let 或 runic 的 $props())属于另一主题,可参考 props 文档

展开属性(Spread Attributes)

展开属性允许一次性把一组属性或 props 传给元素/组件,并且可以与普通属性交错出现。顺序很重要——后出现的属性覆盖先出现的同名项:

<Widget a="b" {...things} c="d" />
  • things.a 存在,它优先于 a="b"(因为展开在 a="b" 之后);
  • c="d" 优先于 things.c(因为 c="d" 在展开之后)。

即:同一位置上的后写者胜,这一语义对元素和组件同样成立。

事件(Events)

on* 事件属性

通过在元素上添加以 on 开头的属性来监听 DOM 事件。例如监听 click 事件,就给按钮加 onclick 属性:

<button onclick={() => console.log('clicked')}>click me</button>

事件属性是大小写敏感的

onclick 监听的是 click 事件,而 onClick 监听的是 Click 事件——两者是不同的事件。这一设计保证了你可以监听名称中含大写字母的自定义事件(例如组件通过 createEventDispatcher 派发的自定义事件)。

事件属性也是属性

因为事件本质上就是属性,所以同样适用属性规则:

  • 简写:<button {onclick}>click me</button>
  • 展开:<button {...thisSpreadContainsEventAttributes}>click me</button>

在时序上,事件属性总是在绑定(bindings)触发的事件之后执行——例如 oninput 总是在 bind:value 更新之后触发。底层实现上,一部分事件处理器通过 addEventListener 直接挂到元素上,另一部分则通过事件委托处理(见下文)。

触摸事件的 passive 优化

使用 ontouchstartontouchmove 事件属性时,处理器会以 passive 模式注册,从而允许浏览器立即滚动文档,而不是等待看事件处理函数是否调用 event.preventDefault(),大幅提升移动端响应性。

从源码看,这一行为由 utils.js 中的 PASSIVE_EVENTS = ['touchstart', 'touchmove'] 决定,源码注释解释了原因:这两个事件触发频繁、多发生在性能更弱的移动设备上,且默认经委托路径处理,因此显式标记为 passive。

极少数确实需要 preventDefault 阻止默认行为的场景,应改用从 svelte/events 导入的 on 函数(例如在 action 内部)。

事件委托(Event Delegation)

为降低内存占用、提升性能,Svelte 对特定事件使用事件委托:在应用根部只挂一个监听器,由它负责执行事件传播路径上各元素的处理器。以下 23 个事件会走委托:

beforeinputclickchangedblclickcontextmenufocusinfocusoutinputkeydownkeyupmousedownmousemovemouseoutmouseovermouseuppointerdownpointermovepointeroutpointeroverpointeruptouchendtouchmovetouchstart

这份清单与 utils.js 中的 DELEGATED_EVENTS 常量逐条一致,并由 can_delegate_event() 供编译器在分析阶段(见 Attribute.js 中的调用)判断某个 on* 属性能否走委托路径。

委托模式的两个陷阱

  1. 手动派发事件:如果你 dispatchEvent 一个使用委托监听器的事件,务必设置 { bubbles: true },否则事件到不了应用根部,处理器不会执行;
  2. 直接使用 addEventListener:避免在其中调用 stopPropagation,否则事件到不了根部、委托处理器不会触发。同时,根部手动添加的处理器会在捕获与冒泡两个阶段都先于深层声明式处理器(如 onclick={...})执行。

因此官方建议:需要手动挂事件时,用从 svelte/events 导入的 on 函数而非 addEventListener,它会保证与声明式处理器的执行顺序正确、并妥善处理 stopPropagation

源码走读:委托处理器如何工作

on 函数定义于 internal/client/dom/elements/events.js,其 JSDoc 明确说明了它存在的意义:

"Attaches an event handler to an element and returns a function that removes the handler. Using this rather than addEventListener will preserve the correct order relative to handlers added declaratively (with attributes like onclick), which use event delegation for performance reasons"

委托的运行时机制分三步(见 events.js):

  1. 注册期delegated(event_name, element, handler) 不挂任何监听器,而是把处理器按事件名存到元素上的一个 event_symbol 符号属性中;delegate(events) 则在根部登记"应用关注哪些事件"。
  2. 触发期:根部监听器调用 handle_event_propagation,它通过 event.composedPath() 拿到完整传播路径,从目标元素起沿路径向上遍历,逐个查找 element[event_symbol][event_name] 并调用,遇到 event.cancelBubble 即停止。
  3. 一致性保证:遍历过程中会临时把 event.currentTarget 代理为当前路径上的真实目标元素,保证委托处理器内 event.currentTarget 语义与原生监听器一致;多个嵌套挂载的应用之间通过 event[event_symbol] 记录"已在何处处理过"来避免重复触发。

也就是说,onclick 在编译产物中并不会真的调用 addEventListener,而是一次"在元素上登记 + 等根部统一分发"的操作——这正是文档提示"手动 dispatchEvent 必须 bubbles"的根本原因。

文本表达式(Text Expressions)

花括号内可写任意 JavaScript 表达式:

{expression}

求值结果为 nullundefined 时会被省略,其余值一律转换为字符串。

在模板中输出字面量花括号

若确实需要在模板里显示 {},使用 HTML 实体:&lbrace;&lcub;&#123; 表示 {&rbrace;&rcub;&#125; 表示 }

正则表达式字面量

在表达式中使用正则字面量(RegExp literal notation)时,必须用圆括号包裹,避免解析歧义:

<h1>Hello {name}!</h1>
<p>{a} + {b} = {a + b}.</p>

<div>{(/^[A-Za-z ]+$/).test(value) ? x : y}</div>

自动转义与 XSS 防护

表达式会被字符串化并转义,以防止代码注入(XSS)。转义逻辑实现在 escaping.jsescape_html(value, is_attr) 中:普通文本转义 &<,属性模式额外转义 "

如果确实需要渲染 HTML,使用 {@html} 标签:

{@html potentiallyUnsafeHtmlString}

[!NOTE] 务必对传入字符串做转义,或只填充你自己完全可控的值,否则会造成 XSS 攻击 风险。{@html} 的完整用法见 08-@html.md

注释(Comments)

HTML 注释

组件内可直接使用标准 HTML 注释:

<!-- this is a comment! --><h1>Hello world</h1>

svelte-ignore

svelte-ignore 开头的注释会关闭其后一段标记上的警告(通常是可访问性警告)。关闭警告务必有充分理由,可用的 a11y 警告清单见 a11y.md

<!-- svelte-ignore a11y_autofocus -->
<input bind:value={name} autofocus />

@component 文档注释

@component 开头的特殊注释,会在其他文件中悬停(hover)该组件名时作为文档提示显示,支持 Markdown 和代码块:

<!--
@component
- You can use markdown here.
- You can also use code blocks here.
- Usage:
  ```html
  <Main name="Aretha">

-->

Hello, {name}

```

标签内的 JS 风格注释

在标签的属性之间,还可以写 JavaScript 风格的行注释:

<div
  // this is a comment!
  data-foo="bar"
>
  foo bar
</div>

小结

Svelte 的基础标记可以概括为一张速查表:

语法 规则要点 源码依据
小写标签 / 大写或点号标签 元素 / 组件 编译器按标签名分流
布尔属性 truthy 保留、falsy 移除 DOM_BOOLEAN_ATTRIBUTES
非布尔属性 null/undefined 时移除 同上转换逻辑
{name} 简写 等价于 name={name},元素与组件通用
{...spread} 后写者优先
on* 属性 大小写敏感、可简写/展开、先于绑定前触发 can_delegate_event
触摸事件 默认 passive PASSIVE_EVENTS
委托事件(23 个) 根部单监听器分发 DELEGATED_EVENTS
{expression} null/undefined 省略,其余转字符串并转义 escape_html
{@html} 原样渲染 HTML,需自行防 XSS @html 文档
<!-- svelte-ignore ... --> / <!-- @component ... --> 关闭警告 / IDE 悬停文档 a11y 警告清单

以上行为以当前仓库(Svelte 6 开发主线)的源码为准;其中"引用单个表达式会在 Svelte 6 中被强制转字符串"一条来自文档自身的版本注记,属于版本敏感行为,跨版本使用时请以对应版本发布说明核对。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384