{@attach ...}
附件(attachments)是一些函数,当元素被挂载到 DOM 时,或当函数内部读取的状态 发生更新时,它们会在一个effect 中运行。
可选地,它们可以返回一个函数,该函数在附件重新运行之前,或元素之后从 DOM 中移除时被调用。
[!NOTE] 附件在 Svelte 5.29 及更高版本中可用。
<!--- file: App.svelte --->
<script>
/** @type {import('svelte/attachments').Attachment} */
function myAttachment(element) {
console.log(element.nodeName); // 'DIV'
return () => {
console.log('cleaning up');
};
}
</script>
<div {@attach myAttachment}>...</div>
一个元素可以拥有任意数量的附件。
附件工厂
一种有用的模式是让某个函数(例如下面示例中的 tooltip)_返回_一个附件(演示):
<!--- file: App.svelte --->
<script>
import tippy from 'tippy.js';
let content = $state('Hello!');
/**
* @param {string} content
* @returns {import('svelte/attachments').Attachment}
*/
function tooltip(content) {
return (element) => {
const tooltip = tippy(element, { content });
return tooltip.destroy;
};
}
</script>
<input bind:value={content} />
<button {@attach tooltip(content)}>
Hover me
</button>
由于 tooltip(content) 表达式是在一个effect 内部运行的,因此每当 content 发生变化时,该附件都会被销毁并重新创建。对于在附件函数首次运行时_内部_读取的任何状态,也会发生同样的情况。(如果这不是你想要的,请参阅控制附件何时重新运行。)
内联附件
附件也可以以内联方式创建(演示):
<!--- file: App.svelte --->
<canvas
width={32}
height={32}
{@attach (canvas) => {
const context = canvas.getContext('2d');
$effect(() => {
context.fillStyle = color;
context.fillRect(0, 0, canvas.width, canvas.height);
});
}}
></canvas>
[!NOTE] 嵌套的 effect 会在
color发生变化时运行,而外部的 effect(即调用canvas.getContext(...)的地方)只运行一次,因为它没有读取任何响应式状态。
条件式附件
像 false 或 undefined 这样的假值会被视为"无附件",从而支持条件式使用:
<div {@attach enabled && myAttachment}>...</div>
将附件传递给组件
当用于组件时,{@attach ...} 会创建一个键为 Symbol 的 prop。如果该组件随后将 props 展开 到一个元素上,该元素就会接收到这些附件。
这让你能够创建_包装组件_,用以增强元素(演示):
<!--- file: Button.svelte --->
<script>
/** @type {import('svelte/elements').HTMLButtonAttributes} */
let { children, ...props } = $props();
</script>
<!-- `props` includes attachments -->
<button {...props}>
{@render children?.()}
</button>
<!--- file: App.svelte --->
<script>
import tippy from 'tippy.js';
import Button from './Button.svelte';
let content = $state('Hello!');
/**
* @param {string} content
* @returns {import('svelte/attachments').Attachment}
*/
function tooltip(content) {
return (element) => {
const tooltip = tippy(element, { content });
return tooltip.destroy;
};
}
</script>
<input bind:value={content} />
<Button {@attach tooltip(content)}>
Hover me
</Button>
控制附件何时重新运行
与actions 不同,附件是完全响应式的:{@attach foo(bar)} 会在 foo 或 bar(或 foo 内部读取的任何状态)发生变化时重新运行:
function foo(bar) {
return (node) => {
veryExpensiveSetupWork(node);
update(node, bar);
};
}
在极少数情况下,这会成为问题(例如,如果 foo 做了昂贵且不可避免的设置工作),可以考虑将数据放在一个函数内部传递,并在一个子 effect 中读取它:
function foo(getBar) {
return (node) => {
veryExpensiveSetupWork(node);
$effect(() => {
update(node, getBar());
});
}
}
以编程方式创建附件
要向一个将被展开到组件或元素上的对象添加附件,请使用 createAttachmentKey。
将 actions 转换为附件
如果你正在使用一个只提供 actions 的库,可以通过 fromAction 将它们转换为附件,从而让你可以(例如)将它们与组件一起使用。