Svelte 模板基础语法详解:从 HTML++ 标记、属性规则到事件委托实现
【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte
本文基于 Svelte 官方文档 Basic markup 展开,系统讲解 Svelte 组件中"HTML++"模板体系的六大核心能力:元素与组件标签的区分规则、静态/动态属性及布尔属性的取值语义、组件 props 与展开属性、on*事件属性及其底层的事件委托机制、花括号文本表达式与 HTML 转义,以及注释的多种形态。读完后,你将不仅会写 Svelte 模板,还能结合仓库源码(如 utils.js、events.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(即
null或undefined)就会出现。
<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列出了allowfullscreen、autofocus、checked、disabled、muted、required、readonly等 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 优化
使用ontouchstart与ontouchmove事件属性时,处理器会以 passive 模式注册,从而允许浏览器立即滚动文档,而不是等待看事件处理函数是否调用event.preventDefault(),大幅提升移动端响应性。
从源码看,这一行为由 utils.js 中的PASSIVE_EVENTS = ['touchstart', 'touchmove']决定,源码注释解释了原因:这两个事件触发频繁、多发生在性能更弱的移动设备上,且默认经委托路径处理,因此显式标记为 passive。
极少数确实需要preventDefault阻止默认行为的场景,应改用从svelte/events导入的on函数(例如在 action 内部)。
事件委托(Event Delegation)
为降低内存占用、提升性能,Svelte 对特定事件使用事件委托:在应用根部只挂一个监听器,由它负责执行事件传播路径上各元素的处理器。以下 23 个事件会走委托:
beforeinput、click、change、dblclick、contextmenu、focusin、focusout、input、keydown、keyup、mousedown、mousemove、mouseout、mouseover、mouseup、pointerdown、pointermove、pointerout、pointerover、pointerup、touchend、touchmove、touchstart
这份清单与 utils.js 中的DELEGATED_EVENTS常量逐条一致,并由can_delegate_event()供编译器在分析阶段(见 Attribute.js 中的调用)判断某个on*属性能否走委托路径。
委托模式的两个陷阱
- 手动派发事件:如果你
dispatchEvent一个使用委托监听器的事件,务必设置{ bubbles: true },否则事件到不了应用根部,处理器不会执行; - 直接使用
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
addEventListenerwill preserve the correct order relative to handlers added declaratively (with attributes likeonclick), which use event delegation for performance reasons"
委托的运行时机制分三步(见 events.js):
- 注册期:
delegated(event_name, element, handler)不挂任何监听器,而是把处理器按事件名存到元素上的一个event_symbol符号属性中;delegate(events)则在根部登记"应用关注哪些事件"。 - 触发期:根部监听器调用 handle_event_propagation,它通过
event.composedPath()拿到完整传播路径,从目标元素起沿路径向上遍历,逐个查找element[event_symbol][event_name]并调用,遇到event.cancelBubble即停止。 - 一致性保证:遍历过程中会临时把
event.currentTarget代理为当前路径上的真实目标元素,保证委托处理器内event.currentTarget语义与原生监听器一致;多个嵌套挂载的应用之间通过event[event_symbol]记录"已在何处处理过"来避免重复触发。
也就是说,onclick在编译产物中并不会真的调用addEventListener,而是一次"在元素上登记 + 等根部统一分发"的操作——这正是文档提示"手动dispatchEvent必须bubbles"的根本原因。
文本表达式(Text Expressions)
花括号内可写任意 JavaScript 表达式:
{expression}求值结果为null或undefined时会被省略,其余值一律转换为字符串。
在模板中输出字面量花括号
若确实需要在模板里显示{或},使用 HTML 实体:{、{、{表示{,}、}、}表示}。
正则表达式字面量
在表达式中使用正则字面量(RegExpliteral notation)时,必须用圆括号包裹,避免解析歧义:
<h1>Hello {name}!</h1> <p>{a} + {b} = {a + b}.</p> <div>{(/^[A-Za-z ]+$/).test(value) ? x : y}</div>自动转义与 XSS 防护
表达式会被字符串化并转义,以防止代码注入(XSS)。转义逻辑实现在 escaping.js 的escape_html(value, is_attr)中:普通文本转义&与<,属性模式额外转义"。
如果确实需要渲染 HTML,使用{@html}标签:
{@html potentiallyUnsafeHtmlString}[!NOTE] 务必对传入字符串做转义,或只填充你自己完全可控的值,否则会造成 XSS 攻击。
注释(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! contenteditable="false">【免费下载链接】svelteweb development for the rest of us
项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考