Svelte5 Attachment
为什么弃用 Svelte4 中的 action?
语法合理化
● 旧语法 use:action = {params} 易产生歧义,参数传递歧义问题(看似平等关系,实为函数调用)
● 新语法 @attach = {fn} 明确表达“将函数附加到元素”的意图
● 支持内联函数:可直接写 @attach = {(node)=>{...}}
● 新语法与 @html 和 @debug 等指令形成视觉系统
响应式增强
● Actions 参数变化不会触发更新
● Attachments 自动跟踪依赖(基于 Runes)
扩展性提升
● 支持组件级附加(而不仅限于DOM 元素)
● 允许合并多个附加逻辑(旧语法需要手动合并动作)
● 为未来动画/过度系统预留统一接口
Svelte5 Attachments
Attachments 是元素挂在到 DOM 时运行的函数,他们可以返回一个函数,该函数在稍后 DOM 中删除元素时调用,此功能官网说需要在 Svelte 5.29 版本以上更新
基础案例
<script lang="ts">
import type { Attachment } from 'svelte/attachments';
const myAttachment: Attachment = (element) => {
console.log(element.nodeName); // 'DIV'
return () => {
console.log('cleaning up');
};
};
const navigatorAttach(){
console.log('navigator')
}
</script>
<div {@attach myAttachment} {@attach navigatorAttach}>...</div>
注意:一个元素可以附加多个 attach
传参案例
<script lang="ts">
import tippy from 'tippy.js'; //一个tip的简单库直接 npm 安装就可以,npm install tippy.js
import type { Attachment } from 'svelte/attachments';
let content = $state('Hello!');
function tooltip(content: string): Attachment {
return (element) => {
const tooltip = tippy(element, { content });
return tooltip.destroy;
};
}
</script>
<input bind:value={content} />
<button {@attach tooltip(content)}>
Hover me
</button>
注意:由于 tooltip 表达式在 $effect 中运行,因此每当 content 更改时, 附件将被销毁并重新创建
执行顺序:
$effect(() => {
console.log('调用effect', count); //第一次 DOM 加载完成就执行,每次更新后,限制性return 内容
return () => {
console.log('destroy'); //DOM 加载完成不执行,下次 count 更改后先执行
};
});
<button onclick={() => count++}>{count}</button>
● 第一次执行 $effect 中的内容,
● 再次更新后,先执行 return 中的内容,在执行 $effect 内容
内联案例
<canvas width=32 height=32 {@attach (canvas)=>{console.log(canvas)}}></canvas>
注意:由于是内联样式,无需传参,因为局部的变量可以直接使用
let config = { speed: 1.5 }; // 动态配置对象
<canvas width=32 height=32 {@attach (canvas)=>{console.log(canvas),console.log(config)}}></canvas>
将 attachments 传递给组件
当在组件上使用时,{@attach} 将创建一个Symbol。 如果组件将道具扩展到元素上,则该元素将接收这些 attach
//Button
<script lang="ts">
import type { HTMLButtonAttributes } from 'svelte/elements';
let { children, ...props }: HTMLButtonAttributes = $props();
</script>
<!-- `props` includes attachments -->
<button {...props}>
{@render children?.()}
</button>
<script>
import tippy from 'tippy.js';
import Button from './Button.svelte';
let content = $state('Hello!');
function tooltip(content) {
return (node) => {
const tooltip = tippy(node, { content });
return tooltip.destroy;
};
}
</script>
<input bind:value={content} />
<Button {@attach tooltip(content)}>
Hover me
</Button>
已编程方式创建 attachments
创建一个对象键,当对象扩展到元素上时,使用 {@attach ...} 的编程替换方案。(createAttachmentKey() 的 Symbol 用于props扩散时的附件标识)
<script>
import { createAttachmentKey } from 'svelte/attachments';
const props = {
class: 'cool',
onclick: () => alert('clicked'),
[createAttachmentKey()]: (node) => {
node.textContent = 'attached!';
}
};
</script>
<button {...props}>click me</button>
function createAttachmentKey(): symbol;
内部原理
内部直接实现使用 effect
import { effect } from '../../reactivity/effects.js';
/**
* @param {Element} node
* @param {() => (node: Element) => void} get_fn
*/
export function attach(node, get_fn) {
effect(() => {
const fn = get_fn();
// we use `&&` rather than `?.` so that things like
// `{@attach DEV && something_dev_only()}` work
return fn && fn(node);
});
}