Snippetのexportで遊ぼう！

## 注意事項
:::note warn
今後動かなくなる可能性があるかもしれません。
自己責任でお願いします。
:::

また、この記事は比較的ニッチな内容を取り扱います。
以下の事前に必要な知識をご存じない方は先にドキュメントを参照ください。

### 事前に必要なちょっと高度な知識
- [Exporting snippets](https://svelte.dev/docs/svelte/snippet#Exporting-snippets)
- [script module](https://svelte.dev/docs/svelte/svelte-files#script-module)

## 本題
皆さんはコンポーネントの中で定義した`Snippet`を`export`し、親で使用したことがありますか？

また、その`Snippet`にリアクティブな値を渡したことがありますか？
この記事ではそんな例を紹介します。

全てのコードは以下のPlaygroundにあります。
https://svelte.dev/playground/66d0481ddba3445b9f0f4e882e4d0aec?version=5.23.0


### Message.svelte
```svelte
<script lang="ts" module>
    // useMessageは他のライブラリなどからimportして使う想定です
	const useMessage = ({ uniqueId }) => {
		const id = $derived(uniqueId());
		return {
			get message() {
				return {
					get props() {
						return {
							'data-message-id': id
						}
					}
				}
			}
		}
	}
	let messageId = $state('id');
	const { message } = useMessage({
		uniqueId: () => messageId
	});
	export { MessageText };
</script>
<script lang="ts">
	import type { Snippet } from 'svelte'
	let { children }: { children: Snippet } = $props();
</script>

<button type="button" onclick={() => (messageId = messageId + ' xxx')}>{messageId}</button>
{#snippet MessageText({ text })}
  <p>id: {message.props['data-message-id']}</p>
  <p>
    {@render text()}
  </p>
{/snippet}
{@render children?.()}
```

ちょっとずつ説明します。

#### useMessage
```ts
	const useMessage = ({ uniqueId }) => {
		const id = $derived(uniqueId());
		return {
			get message() {
				return {
					get props() {
						return {
							'data-message-id': id
						}
					}
				}
			}
		}
	}
```

`useMessage`は`$state`などリアクティブな値を関数で受け取り、`message.props`に`data-message-id: {id}`を返す関数です。

意味がわからない例ですが、要するに`useMessage`の引数にリアクティブな`id`を渡したらそれに応じた`data-message-id`属性を返すような関数です。

関数の引数の変更を返り値に反映するには関数で渡す必要があります。
[ドキュメント](https://svelte.dev/docs/svelte/$state#Passing-state-into-functions)には`Class`を使えと書いてありますが、自分が`Class`を気に入っていないのであまり気にせずそうしています。

#### useMessageにリアクティブな値を渡す
```ts
	let messageId = $state('id');
	const { message } = useMessage({
		uniqueId: () => messageId
	});
```

`useMessage`に関数で渡しています。

自分が知らなかったポイントとしては`<script lang="ts" module>`の中でRuneが使えるということです。
全然知らなかった…

### export { MessageText };
```ts
export { MessageText };
```

残りのこの部分はテンプレート部分で定義した`Snippet`の`export`です。

### テンプレート部分
```svelte
<button type="button" onclick={() => (messageId = messageId + ' xxx')}>{messageId}</button>
{#snippet MessageText({ text })}
  <p>id: {message.props['data-message-id']}</p>
  <p>
    {@render text()}
  </p>
{/snippet}
{@render children?.()}
```

- ボタンをクリックすると`$state`で管理している値に` xxx`が追加されていきます
- `MessageText`という`Snippet`を定義し、引数として`text`という名前の`Snippet`を受け取り、表示します
- `useMessage`の返り値から`data-message-id`を表示します
- デフォルト`Snippet`の`children`を表示します

`MessageText`は直接使用せず、HTMLを絞った状態で親側から使用できるように作ってあります。
DOMを縛れるのは大きいかもしれません。

また、最終的にはこの`Message.svelte`を呼ぶ側で`MessageText`を`render`し、
```svelte
<Message>
	{@render MessageText({text})}
</Message>
```
こんな感じで書けば`Message.svelte`の`{@render children?.()}`この部分で表示されます。

`MessageText`を`export`したことで表示する順番や位置が縛られなくなりましたね。

### App.svelte
```svelte
<script lang="ts">
	import Message, { MessageText } from './Message.svelte'
	let message = $state('message')
</script>

<button type="button" onclick={() => message = message + '!'}>
	add message `!`
</button>

<Message>
	{#snippet text()}
		{message}
	{/snippet}
	{@render MessageText({text})}
</Message>
```

`MessageText`を`Message.svelte`から`import`し、`<Message>`の`children` `Snippet`として`render`しています。

というわけで、`Snippet`の`export`をうまく使うと好きにDOMを縛りつつ、しかし柔軟に表示を使う側に委ねるような使い方ができました。

慣れていないと結構わかりにくい気はしますが、ライブラリ制作などで結構役立つかもしれません。
他にも面白い書き方があれば教えてください :thumbsup: