
Svelte 新機能の @attach みてみる
Posted on 2025/05/17
# Technology
svelte@5.29.0 に @attach
という新しい Template Syntax が追加されました。
追加の背景と機能についてみていきたいと思います。
PR: https://github.com/sveltejs/svelte/pull/15000
Docs: https://svelte.dev/docs/svelte/@attach
Demo: Playground
// Syntax
<script lang='ts'>
import type { Attachment } from 'svelte/attachments';
import tippy from 'tippy.js';
let content = $state('Hello!');
function tooltip(content: string): Attachment<HTMLElement> {
return (node) => {
const tooltip = tippy(node, { content });
return tooltip.destroy;
};
}
</script>
<input bind:value={content} />
<button {@attach tooltip(content)}>
Hover me
</button>
背景
Mount 時に Dom Element に対して処理を行いたい場合には Actions
というAPIが提供されてました。
<script lang="ts">
import type { Action } from 'svelte/action';
import tippy from 'tippy.js';
let content = $state('Hello!');
function tooltip(node, fn): Action {
$effect(() => {
const tooltip = tippy(node, fn());
return tooltip.destroy;
});
}
</script>
<input bind:value={content} />
<button use:tooltip={() => ({ content })}>
Hover me
</button>
これが抱える問題として以下が挙げられてました。
<div use:foo={bar}>
は一見するとfoo
とbar
が等しいことを意味しているように見えますが、実際にはfoo(div, bar)
という意味になります。文法がおかしく見た目だけではその意味を推測することは難しいです。use:foo
の foo は識別子でなければなりません。たとえばuse:createFoo()
のように関数呼び出しを直接使うことはできず、どこか別の場所で宣言されている必要があります。- そのため
inline actions
を使うことができません。 - リアクティブではありません。もし
foo
が変化してもuse:foo={bar}
は再実行されません。bar
が変化した場合にはfoo
が update メソッドを返していればそれは再実行されますが、そうでない場合ドキュメントが推奨しているように effects を使っても何も起こりません。 - コンポーネントに対して使うことはできません。
- スプレッドできないので、属性と振る舞いの両方を追加したい場合には jump through hoops が必要になります。
Actions are neat but they have a number of awkward characteristics and limitations:
- the syntax is very weird!
<div use:foo={bar}>
implies some sort of equality betweenfoo
andbar
but actually meansfoo(div, bar)
. There's no way you could figure that out just by looking at it- the
foo
inuse:foo
has to be an identifier. You can't, for example, douse:createFoo()
— it must have been declared elsewhere- as a corollary, you can't do 'inline actions'
- it's not reactive. If
foo
changes,use:foo={bar}
does not re-run. Ifbar
changes, andfoo
returned anupdate
method, that method will re-run, but otherwise (including if you use effects, which is how the docs recommend you use actions) nothing will happen- you can't use them on components
- you can't spread them, so if you want to add both attributes and behaviours you have to jump through hoops
We can do much better.
@attach について
@attach
を使えばより直感的かつみやすく実装できるようになりました。
<script lang='ts'>
import type { Attachment } from 'svelte/attachments';
import tippy from 'tippy.js';
let content = $state('Hello!');
function tooltip(content: string): Attachment<HTMLElement> {
return (node) => {
const tooltip = tippy(node, { content });
return tooltip.destroy;
};
}
</script>
<input bind:value={content} />
<button {@attach tooltip(content)}>
Hover me
</button>
より直感的になりましたね。<div {@attach (node) => console.log(node)}>...</div>
のように Inline で定義できるようにもなりました。
下記のようにすれば Component にも使うことができます。
// Button.svelte
<script>
let { children, ...props } = $props();
</script>
<button {...props}>{@render children?.()}</button>
// App.svelte
<Button
class="cool-button"
onclick={() => console.log('clicked')}
{@attach tooltip(content)}
>
hello
</Button>
@attach
により Dom に対する処理がよりやりやすくなったと思います。
Svelteの update は開発体験を上げるものが多く、Svelte を書いてて本当に楽しいです。
Table of contents