Svelte5でコンポーネントからexportした関数を呼び出し側から実行する方法

## コード
https://github.com/shamokit/svelte5-examples/blob/main/src/routes/%2Bpage.svelte

早い話ここを見てください。
…
で終わると記事にしている意味がないので説明します。

最終的にやりたいのは、

- **コンポーネントを$stateで書き換わる値によって出し分ける**
- 出し分けた上で、**出し分けられたコンポーネントがexportしている関数を実行する**

です。
いくつか前持って知っておく必要のあることがあるので順番に説明します。

## Svelteではコンポーネントから関数をexportできる

```svelte: Component.svelte
<script lang="ts">
	export const execute = () => {
		console.log('test')
	};
</script>
```
または

```svelte: Component.svelte
<script lang="ts">
	export function execute(){
		console.log('test')
	};
</script>
```

このようにコンポーネントを作っておくと、`Component.svelte`を呼び出す側で`execute`関数を実行することができます。

モーダルコンポーネントに`close`を生やしておく　などの使い方ができます。

## Component型
Svelte4では`SvelteComponent`型でしたが、Svelte5からは`Component`型に変わっています。

定義は
```ts
interface Component<
  Props extends Record<string, any> = {},
  Exports extends Record<string, any> = {},
  Bindings extends keyof Props | "" = string
>
```
このようになっており、一つ目の型引数にはprops、二つ目の型引数にはイベント、三つ目の型引数にはbindするpropsの型を指定します。

今回は出し分けるコンポーネントがどちらも同じ名前の関数を`export`しておいて欲しいので、出し分け対象のコンポーネントが同じ名前の関数を持っていることを型で保証するためにこちらを使用します。


## Svelte5からコンポーネントはクラスではなく関数になった
Svelte4まではコンポーネントはクラスでした。

Svelte5からは関数になったので、コンポーネントからexportした関数の型を得るためには`ReturnType`を使用する必要があります。

今回で言うと、`bind:this={bindButton}`するときの`bindButton`の型にこれを使用します。

## 説明終わり
ここまでで説明は終わりです。
実際のコードを見ていきましょう。

## `AddButton.svelte`
```svelte: AddButton.svelte
<script lang="ts">
	let {
		count = $bindable(0),
	}: {
		count: number;
	} = $props();

	export const execute = () => {
		count += 1;
	};
</script>

<button type="button" onclick={() => execute()}
	>{count}からカウントを+1する</button
>
```
bindableなprops、countを受け取ります。

また、`execute`関数をコンポーネント内にあるボタン、もしくは外部から実行することができます。

何でこんなことをしているのかわからないですが、どうやら**ボタンのクリック以外の任意のタイミング**で`count`を増やしたいみたいです。

bindableな`count`にしているのでこの例では`execute`関数を`export`する意味は全くないのですが、~~あまり複雑なコンポーネントを作ろうとすると記事を書くのが面倒になるので~~今回はこれで許してください…。

`MinusButton`の方は`count`を1マイナスします。

## `+page.svelte`
`AddButton`、`MinusButton`の二つを`import`しています。

script部分はこうなっています。

```svelte
<script lang="ts">
	import type { Component } from 'svelte';
	import AddButton from '../lib/AddButton.svelte';
	import MinusButton from '../lib/MinusButton.svelte';

	let count = $state(0);
	let componentName = $state<'AddButton' | 'MinusButton'>('AddButton');
	let ButtonComponent = $derived<
		Component<
			{
				count: number;
			},
			{
				execute: () => void;
			}
		>
	>(componentName === 'AddButton' ? AddButton : MinusButton);

	let bindButton = $state<ReturnType<typeof ButtonComponent>>();
</script>

<h1>Examples</h1>

<h2>bindされたコンポーネントのexecuteを実行する</h2>
<label for="component-name">コンポーネント</label>
<select id="component-name" bind:value={componentName}>
	<option value="AddButton">AddButton</option>
	<option value="MinusButton">MinusButton</option>
</select>
<ul>
	<li><ButtonComponent bind:count bind:this={bindButton}></ButtonComponent></li>
	<li>
		<button type="button" onclick={() => bindButton?.execute()}
			>コンポーネントのexecuteを実行</button
		>
	</li>
</ul>

<p>count： {count}</p>
```

- `count`をbindしたいので`$state`で持っている
- `componentName`が`$state`で管理されており、セレクトボックスの値とbindされている
- `componentName`を元に`ButtonComponent`で`AddButton`と`MinusButton`を出し分けている
- `ButtonComponent`に`bind:this={bindButton}`が書かれている
- `bindButton?.excute()`がボタンに書かれている


結構複雑ですが、これまでに説明してきた内容をフル活用すると結構いろんなことができるのではないか？という気持ちになってきているのではないでしょうか。

`ButtonComponent`で動的に出し分けられたコンポーネントに対してbindしておくと、そのコンポーネントが`export`している関数を実行することができます。

これを応用すると、例えばトーストのような配列から複数の同じ種類のコンポーネントを表示する場合、それらをbindしておき、その中の一つに対して生やしておいた`close`を実行することで任意のトーストだけを画面上から消し去ったりすることができます。

もちろん配列にstateを持っておいて表示非表示を切り替えたり単純に配列から消すなどの対応でもうまく動きますが、トーストを画面から消すタイミングでAPIにリクエストを投げてDBと同期する　みたいな処理も今回の実装であれば簡単に対応することができます。

ちょっと難しい使い方にはなりますが、応用の幅は広そうです。
機会があれば試してみてください。