コード
早い話ここを見てください。
…
で終わると記事にしている意味がないので説明します。
最終的にやりたいのは、
- コンポーネントを$stateで書き換わる値によって出し分ける
- 出し分けた上で、出し分けられたコンポーネントがexportしている関数を実行する
です。
いくつか前持って知っておく必要のあることがあるので順番に説明します。
Svelteではコンポーネントから関数をexportできる
<script lang="ts">
export const execute = () => {
console.log('test')
};
</script>
または
<script lang="ts">
export function execute(){
console.log('test')
};
</script>
このようにコンポーネントを作っておくと、Component.svelte
を呼び出す側でexecute
関数を実行することができます。
モーダルコンポーネントにclose
を生やしておく などの使い方ができます。
Component型
Svelte4ではSvelteComponent
型でしたが、Svelte5からはComponent
型に変わっています。
定義は
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
<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部分はこうなっています。
<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と同期する みたいな処理も今回の実装であれば簡単に対応することができます。
ちょっと難しい使い方にはなりますが、応用の幅は広そうです。
機会があれば試してみてください。