はじめに
最近はじめて実案件でSvelteKitを使用して開発しました。
元々Vue(Nuxt)で主に開発していて、「Svelteだとこれはどうするの?」と思ったポイントをまとめてみました。
Svelteの基本的な文法の解説はこの記事では扱いませんので、
公式チュートリアルなど他の記事を参考にされてください。
増えたらどんどん追記していく予定です。
では早速!
レイアウトで名前付きSlotを使いたい
PCの管理画面系だとこんな感じのレイアウトがよくあると思います。
サイドメニューがあって、画面上部にタイトルとアクションボタン、中央にコンテンツの配置です。
SvelteKitではこういった共通レイアウトを定義する方法が用意されています。
適用したいページと同じ階層か上位の階層に+layout.svelte
で保存しておけば、自動でそのHTMLが読み込まれます。
さて共通レイアウトといっても、アクションボタンや画面タイトルの部分は当然各画面によって変わってきます。
Vueではレイアウトで名前付きスロットが使えるのでそのあたりも簡単に対応できるのですが、悲しきかな、+layout.svelte
では名前付きスロットが使えません。
2019年に立てられたissueですが、未だ実装されていないので対応されないのかもしれません(涙)
代替案がこのissueの中でたくさん挙げられているのですが、その中のコンポーネントを作成してレイアウトのように使う方法でわたしは対応しました。
Svelteでもコンポーネントでは名前付きスロットが使えます。
レイアウトを適用した+page.svelte
がある階層に_customLayout.svelte
を作成します(ファイル名はなんでもOK)。
_customLayout.svelte
では名前付きスロットを定義します。
<div>
<slot name="title" />
<slot name="headerActions" />
</div>
+page.svelte
で_customLayout.svelte
をimportし、スロット部分に挿入したいHTMLを入れます。slotの内部を定義するときは<svelte:fragment>
タグを使います。
<script lang="ts">
import CustomLayout from "../_customLayout.svelte"
</script>
<CustomLayout>
<svelte:fragment slot="title">
<div>一覧画面</div>
</svelte:fragment>
<svelte:fragment slot="headerActions">
<button>検索</button>
<button>新規作成</button>
</svelte:fragment>
</CustomLayout>
これで名前付きスロットを使って画面によって動的に変更することができます。
他にもやり方は色々ありますが、ボタン押下時の処理が画面ファイル内で完結できる点や、そもそもオートインポートなど便利だと思う反面、参照元が追いづらくてあまり好きじゃないタイプなので、こちらの方法を取りました。
コンポーネント親子間での値の受け渡し
親(呼び出し元)、子(呼び出されるコンポーネント)です。
親から子
子側で値を受け取る変数をエクスポートします。
export let title = "デフォルト値";
親側は子のコンポーネントの属性として値を渡します。
<script>
import Child from "$lib/components/Child.svelte"
</script>
<Chiled title="親ページ" />
この場合子側でtitle
の値を変更しても、親側には反映されません(子から親の値を変更する方法は後述)。
子から親
値渡しというよりかは子側の変数を親で参照したいとき
子側は同じように親で参照したい変数をエクスポートします。
export let count = 0;
親側に値を受け取る変数を用意し、バインドします。
<script>
import Child from "$lib/components/Child.svelte"
let count = 0;
</script>
<Chiled title="親ページ" bind:count={count} />
これで子側の変数を参照できます。
この場合変数をバインドしているので、親側で値を変更することも可能。
親→子、子→親どちらも値と同じように関数も渡せる(JSは関数もオブジェクト)ので、親の関数を子に渡して実行、子で定義した関数を親で実行することもできます。
子から親の値を変更したい
前述のようにコンポーネントのpropsとして受け取った変数は子側で変更しても親には反映されません。
反映したい時はcreateEventDispatcherを使って子から親に値をdispatchします。
Vueでいうemitの機能ですね。
<script>
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
export let inputValue = ""
const updateInput = (event: any) => {
dispatch("updateInput", {value: event.detail.value})
}
</script>
<input
type="text"
on:input={(event) => updateInput(event)}
bind:value={inputValue}
/>
親は子からイベントとして値を受け取ります。event.detailに値が入ってきます。
<script>
import Child from "$lib/components/Child.svelte"
let inputValue = ""
</script>
<Chiled
title="親ページ"
{inputValue}
on:updateInput={(event) => {
inputValue = event.detail.value
}}
/>
基本的には入力フォームなど画面からの入力を受け付ける部分をコンポーネント化している時に使い、コード内で変更する値は親子で変更が必要な方で更新を行い、片方は参照だけする、とした方がすっきりしたコードになると思っています
おわりに
比較的新しいフレームワークなのもあり機能の充実度ではVueに劣りますが、
書き方のお作法がVueより少ないかつ単純なので、JSフレームワーク初学者には向いているんではないかと思います。
少しでも読んだ方の参考になれば幸いです。