Zenn
😊

【v4からv5に切り替わる大変な時に勉強してるあなたへ】Svelteの基本的な書き方

2025/01/05に公開
1

はじめに

皆さん、こんにちは。

今回はSvelteの基本的な使い方をご紹介します。最近Svelteはバージョンが上がり新しくルーンという表現が追加されました。この書き方の違いについても掲載していきます。

https://svelte.dev/docs/svelte/v5-migration-guide

サンプルコードのリポジトリ

https://github.com/peter-norio/svelte/tree/main/my-svelte-app

雛形の作成とエディタの設定

概要

  • npm create vite@latestで雛形を作成
  • VSCodeに「Svelte for VS Code」を追加
  • npm run devでアプリの起動
  • WebブラウザでSvelteを気軽に試す(Playground)

Svelteアプリケーションの始め方として雛形の作成とVSCodeの拡張機能をご紹介します。

npm create vite@latestで雛形を作成

Svelteの雛形はviteを直接使うことで作成可能です。

TypeScriptの雛形

npm create vite@latest my-svelte-app -- --template svelte-ts

JavaScriptの雛形

npm create vite@latest my-svelte-app -- --template svelte

作成した雛形のフォルダ内には多数のファイルやフォルダが配置されています。この中のsrcフォルダ内にソースコードを配置します。雛形の時点ではサンプルコードが配置されています。

VSCodeに「Svelte for VS Code」を追加

SvelteチームはVSCodeの拡張機能を公開しています。これを導入することで.svelteファイルでシンタックスハイライトや補完が効くようになります。

設定からシングルクォートを利用するよう設定しておくとオートフォーマットで勝手にダブルクォートにされるのを防げます。

npm run devでアプリの起動

作成した雛形の起動は次のコマンドを順番に実行します。

cd my-svelte-app
npm install
npm run dev

起動後「http://localhost:5174/」へアクセスしてブラウザで確認します。雛形はシンプルなカウンターアプリです。

WebブラウザでSvelteを気軽に試す(Playground)

PC上に環境構築をせずブラウザ上で気軽に試すこともできます。

Svelte公式が用意しているPlaygroundにアクセスし、Svelteコードを試すことができます。

https://svelte.dev/playground/

参考リンク集

コンポーネントの記述

概要

  • .svelteファイルでコンポーネントを記述
  • HTMLをベースに<script>と<style>を使う
  • {式} で値を埋め込む

.svelteファイルでコンポーネントを記述

Svelteのコンポーネントは「.svelte」ファイルで作成します。慣習的にファイル名は大文字で開始します。

HTMLをベースに<script>と<style>を使う

Svelteでは、HTMLをベースにして<script><style>を組み合わせてコンポーネントを記述します。この仕組みは、構造 (HTML)、ロジック (JavaScript)、デザイン (CSS) を1つのファイルにまとめて記述できるのが特徴です。

TypeScriptを使用する場合、<script>タグにlang="ts"属性を追加します。

Svelteでは、デフォルトでCSSがコンポーネントのスコープ内に限定されます。他のコンポーネントに影響を与えません。

<script lang="ts">
	ロジック
</script>

構造

<style>
	デザイン
</style>

{式} で値を埋め込む

Svelteでは、HTML内に{式}を使用して、JavaScriptの値や式を直接埋め込むことができます。この仕組みによって、コンポーネント内で動的に内容をレンダリングすることができます。

<!-- scriptタグ内に変数や関数を定義 -->
<script lang="ts">
  let name = 'svelte';
</script>

<!-- {式}で値を埋め込み  -->
<h1>Hello {name}!</h1>
<p>{1 + 1}</p>

Svelteでは、HTMLタグの属性にも{式}を使用して動的に値を埋め込むことができます。これにより、変数や計算結果を属性値として利用できます。

属性名と変数名が一致している場合は省略表現があります。

<!-- scriptタグ内に変数や関数を定義 -->
<script lang="ts">
  let href = 'https://www.google.com';
</script>

<!-- 属性にも{式}で値を設定 -->
<a href={href}>googleへ</a>

<!-- 属性名と変数名が一致してる場合 -->
<a {href}>googleへ</a>

Basic.svelte

<!-- scriptタグ内に変数や関数を定義 -->
<script lang="ts">
  let name = 'svelte';
  let href = 'https://www.google.com';
</script>

<!-- {式}で値を埋め込み  -->
<h1>Hello {name}!</h1>
<p>{1 + 1}</p>

<!-- 属性にも{式}で値を設定 -->
<a href={href}>googleへ</a>

<!-- 属性名と変数名が一致してる場合 -->
<a {href}>googleへ</a>

<!-- styleタグ内にスタイルを記述 -->
<style>
  h1 {
    color: blue;
  }
</style>

参考リンク集

リアクティブな値

概要

  • $state(初期値) でリアクティブな値を用意
  • 更新でリアクティブに動作
  • $derived(依存state) で連動して動作

$state(初期値) でリアクティブな値を用意

Svelte5でリアクティブな値を宣言するには$state(初期値)を使います。基本データ型の値を扱う場合はletで宣言します。これは変数への再代入をきっかけにリアクティブに動作するためです。

<script lang="ts">
  //リアクティブな変数を定義(再代入をきっかけにするのでlet)
  let count = $state(0);
</script>

<div>
  <h1>Reactive</h1>
  {count}
  <!-- 再代入でリアクティブに動作 -->
  <button onclick={() => (count += 10)}>plus</button>
</div>

更新でリアクティブに動作

リアクティブな値を更新することで画面も更新されます。基本データ型の場合は再代入により更新されます。Svelte5ではオブジェクトや配列の場合は中身を直接変更することで更新のきっかけになります。

<script lang="ts">
  // 配列やオブジェクトもリアクティブにすることができる
  const arr = $state([1, 2, 3, 4, 5]);
  const obj = $state({ name: 'svelte', age: 1 });
</script>

<div>
  <h2>リアクティブな配列</h2>
  {arr}
  <!-- 配列を直接操作してリアクティブに動作 -->
  <button onclick={() => arr.push(arr.length + 1)}>push</button>

  <h2>リアクティブなオブジェクト</h2>
  {obj.age}
  <!-- オブジェクトのプロパティを直接操作してリアクティブに動作 -->
  <button onclick={() => (obj.age += 1)}>push</button>
</div>

$derived(依存state) で連動して動作

特定のstateの変更に連動して値を用意したい場合は$derived(依存state) を利用します。引数には依存するstateを利用した値を指定します。$derivedで用意した変数は読み取り専用として扱われます。

<script lang="ts">
  //リアクティブな変数を定義(再代入をきっかけにするのでlet)
  let count = $state(0);

  // 他のstateの変更を監視してリアクティブに動作
  const double = $derived(count * 2); // countが変更されると再計算される
</script>

<div>
  <h1>Reactive</h1>
  {count}
  <!-- 再代入でリアクティブに動作 -->
  <button onclick={() => (count += 10)}>plus</button>

  <h2>他のStateに依存するState</h2>
  {double}
</div>

$derived の引数がシンプルな形式にできない場合は 、$derived.by(関数) とすることもできます。引数の関数内で参照しているstateのいずれかに変更がある場合に再計算されます。

<script lang="ts">
  //リアクティブな変数を定義(再代入をきっかけにするのでlet)
  let count = $state(0);

  // 配列やオブジェクトもリアクティブにすることができる
  const arr = $state([1, 2, 3, 4, 5]);

  // 他のstateの変更を監視してリアクティブに動作
  const sumXcount = $derived.by(() => {  // countかarrが変更されると再計算される
    let results = 0;
    for (const item of arr) {
      results += item;
    }
    return results * count;
  });
</script>

<div>
  <h1>Reactive</h1>
  {count}
  <!-- 再代入でリアクティブに動作 -->
  <button onclick={() => (count += 10)}>plus</button>

  <h2>リアクティブな配列</h2>
  {arr}
  <!-- 配列を直接操作してリアクティブに動作 -->
  <button onclick={() => arr.push(arr.length + 1)}>push</button>

  <h2>他のStateに依存するState</h2>
  {sumXcount}
</div>

Reactive.svelte

<script lang="ts">
  //リアクティブな変数を定義(再代入をきっかけにするのでlet)
  let count = $state(0);

  // 配列やオブジェクトもリアクティブにすることができる
  const arr = $state([1, 2, 3, 4, 5]);
  const obj = $state({ name: 'svelte', age: 1 });

  // 他のstateの変更を監視してリアクティブに動作
  const double = $derived(count * 2); // countが変更されると再計算される
  const sumXcount = $derived.by(() => {  // countかarrが変更されると再計算される
    let results = 0;
    for (const item of arr) {
      results += item;
    }
    return results * count;
  });
</script>

<div>
  <h1>Reactive</h1>
  {count}
  <!-- 再代入でリアクティブに動作 -->
  <button onclick={() => (count += 10)}>plus</button>

  <h2>リアクティブな配列</h2>
  {arr}
  <!-- 配列を直接操作してリアクティブに動作 -->
  <button onclick={() => arr.push(arr.length + 1)}>push</button>

  <h2>リアクティブなオブジェクト</h2>
  {obj.age}
  <!-- オブジェクトのプロパティを直接操作してリアクティブに動作 -->
  <button onclick={() => (obj.age += 1)}>push</button>

  <h2>他のStateに依存するState</h2>
  {double}
  {sumXcount}
</div>

参考リンク集
v4の書き方

サンプルは上記と同等の内容を再現しています。

letでリアクティブな変数の定義

v4以前では、リアクティビティの実現には、letによる変数宣言を用います。letは再代入可能な変数であり、再代入時にSvelteはその変更を検知して、UIを再レンダリングします。

//リアクティブな変数を定義(再代入をきっかけにするのでlet)
let count = 0;

// 配列やオブジェクトもリアクティブにすることができる
let arr = [1, 2, 3, 4, 5];
let obj = { name: "svelte", age: 1 };

再代入でリアクティブに動作(配列もオブジェクトも)

v4でも再代入をきっかけにリアクティブに動作しますが、配列やオブジェクトもメソッドなどでの直接操作は変更を検知しないので、再代入が必要になります。その際は (スプレッド構文)を利用し新しいオブジェクトや配列を用意します。

// 基本データ型の更新
count += 10;

// 配列の更新
arr = [...arr, length + 1];

// オブジェクトの更新
obj = { ...obj, age: obj.age + 1 };

$: でリアクティブステートメント

v4において$:は、変数が変更されたときに自動的に再実行されるリアクティブステートメントを定義します。依存するリアクティブな変数が変更されたときに連動して動作します。

// 他のstateの変更を監視してリアクティブに動作
$: double = count * 2; // countが変更されると再計算される
$: sumXcount = (() => {
  // countかarrが変更されると再計算される
  let results = 0;
  for (const item of arr) {
    results += item;
  }
  return results * count;
})();

ReactiveV4.svelte

<script lang="ts">
  //リアクティブな変数を定義(再代入をきっかけにするのでlet)
  let count = 0;

  // 配列やオブジェクトもリアクティブにすることができる
  let arr = [1, 2, 3, 4, 5];
  let obj = { name: 'svelte', age: 1 };

  // 他のstateの変更を監視してリアクティブに動作
  $: double = count * 2; // countが変更されると再計算される
  $: sumXcount = (() => {
    // countかarrが変更されると再計算される
    let results = 0;
    for (const item of arr) {
      results += item;
    }
    return results * count;
  })();
</script>

<div>
  <h1>Reactive</h1>
  {count}
  <!-- 再代入でリアクティブに動作 -->
  <button onclick={() => (count += 10)}>plus</button>

  <h2>リアクティブな配列</h2>
  {arr}
  <!-- 配列を再代入でリアクティブに動作 -->
  <button onclick={() => (arr = [...arr, length + 1])}>push</button>

  <h2>リアクティブなオブジェクト</h2>
  {obj.age}
  <!-- オブジェクトのプロパティを直接操作してリアクティブに動作(v5と同じ) -->
  <button onclick={() => (obj = { ...obj, age: obj.age + 1 })}>push</button>

  <h2>他のStateに依存するState</h2>
  {double}
  {sumXcount}
</div>

https://learn.svelte.jp/tutorial/reactive-assignments

https://learn.svelte.jp/tutorial/reactive-declarations

https://learn.svelte.jp/tutorial/reactive-statements

https://learn.svelte.jp/tutorial/updating-arrays-and-objects

親子のコンポーネントとprops

概要

  • <子コンポーネント props名={値}> で子に値を渡す
  • <子コンポーネント {...オブジェクト}> でまとめてpropsを渡す
  • $props で親からの値を受け取る

<子コンポーネント props名={値}> で子に値を渡す

親から子にpropsという仕組みで値を渡すことができます。子コンポーネントに値を渡すときは属性のように記述します。

<Child item={'child'}/> 

<子コンポーネント {...オブジェクト}> でまとめてpropsを渡す

propsは複数渡せます。オブジェクトを用意してまとめて渡すこともできます。

<script lang="ts">
  import Child from './components/Child.svelte';
  const data = { name: 'svelte', message: 'Hello' };
</script>

<Child {...data} />

$props で親からの値を受け取る

子コンポーネント内で $props を使用すると、親から渡されたすべてのプロパティをオブジェクトとして取得できます。受け取る際は分割代入を利用します。

const { item } = $props();

デフォルト値を指定することもできます。

const { item2 = 'xxx' } = $props();

App.svelte(親)

<script lang="ts">
  import Child from './components/Child.svelte';
  const data = { name: 'svelte', message: 'Hello' };
</script>

<Child item={'child'} {...data} />

Child.svelte(子)

<script lang="ts">
  // propsを分割代入で受け取る
  // item2はデフォルト値を設定
  const { item, item2 = 'xxx', name, message } = $props();
</script>

<div>
  <h1>Child</h1>
  <p>props value is {item}</p>
  <p>default value is {item2}</p>
  <p>spread valus are {name}, {message}</p>
</div>

参考リンク集
v4の書き方

サンプルは上記と同等の内容を再現しています。

export let でprops宣言(値を受け取る)

v4では親コンポーネントから渡された値(props)を受け取る際に、子コンポーネント内で export let を使用して宣言します。この仕組みでデータを受け渡し、子コンポーネントで利用可能になります。

App.svelte(親)

<script lang="ts">
  import ChildV4 from './components/ChildV4.svelte';
  const data = { name: 'svelte', message: 'Hello' };
</script>

<ChildV4 item={'child'} {...data} />

ChildV4.svelte(子)

<script lang="ts">
  // export letで受け取る
  export let item;
  // item2はデフォルト値を設定
  export let item2 = 'xxx';

  export let name;
  export let message;

</script>

<div>
  <h1>Child</h1>
  <p>props value is {item}</p>
  <p>default value is {item2}</p>
  <p>spread valus are {name}, {message}</p>
</div>

https://learn.svelte.jp/tutorial/nested-components

https://learn.svelte.jp/tutorial/declaring-props

https://learn.svelte.jp/tutorial/default-values

https://learn.svelte.jp/tutorial/spread-props

ロジックブロック

概要

  • {#if 条件} で条件による表示の切り替え
  • {#each 配列 as 要素} で配列の要素を繰り返し表示
  • eachブロックで要素番号やキーを扱う

{#if 条件} で条件による表示の切り替え

条件による表示の切り替えに {#if 条件} を使用します。この構文を使うと、条件が true の場合に特定の要素やコンテンツを表示し、false の場合には表示しないようにできます。

else を追加することで、条件が false の場合に表示する内容も指定できます。

複数の条件を分岐したい場合は {:elseif} を使います。

{#if isOk}
  <p>It's ok</p>
{:else if isOk === null}
  <p>It's null</p>
{:else}
  <p>It's not ok</p>
{/if}

{#each 配列 as 要素} で配列の要素を繰り返し表示

{#each} ブロックを使用して、配列の要素を繰り返し表示することができます。複数の要素を動的に生成する際に便利です。

<script lang="ts">
  const list = $state([1, 2]);
</script>

<h1>LogicBlock</h1>
<div>
  <button onclick={() => list.push(list.length + 1)}>push</button>
  <!-- 配列の要素数に合わせて繰り返し表示 -->
  {#each list as num}
    <div>{num}</div>
  {/each}
</div>

eachブロックで要素番号やキーを扱う

{#each}ブロックでは、要素番号(インデックス)やキーを利用することで、リストのアイテムを効率的に操作できます。

{#each}ブロックで要素番号(インデックス)を取得するには、as の後にカンマ区切りで指定します。リストの変更時にDOMの更新を効率化するために、key を指定することができます。key は各要素を一意に識別するための値で、要素のプロパティなどから設定します。

{#each tools as item, idx (item.id)}
  <div>{idx} {item.name}</div>
{/each}

LogicBlock.svelte

<script lang="ts">
  let isOk = $state(false);
  const list = $state([1, 2]);
  const tools = $state([
    { id: 1, name: 'svelte' },
    { id: 2, name: 'react' },
    { id: 3, name: 'vue' },
  ]);
</script>

<h1>LogicBlock</h1>
<div>
  <button onclick={() => (isOk = !isOk)}>Toggle</button>
  <!-- 条件によって表示を切り替え -->
  {#if isOk}
    <p>It's ok</p>
  {:else if isOk === null}
    <p>It's null</p>
  {:else}
    <p>It's not ok</p>
  {/if}
</div>
<div>
  <button onclick={() => list.push(list.length + 1)}>push</button>
  <!-- 配列の要素数に合わせて繰り返し表示 -->
  {#each list as num}
    <div>{num}</div>
  {/each}
</div>
<div>
  <h4>インデックス:名前</h4>
  <!-- {#each 配列 as 要素, インデックス (キー)} -->
  {#each tools as item, idx (item.id)}
    <div>{idx} {item.name}</div>
  {/each}
</div>

参考リンク集

非同期データの表示

概要

  • {#await プロミス} でPromiseが解決されるまで表示を待機
  • {#await プロミス then 結果}で待機画面のない簡易的な表現

{#await プロミス} でPromiseが解決されるまで表示を待機

{#await}ブロックは、Promiseの状態に応じた表示を簡単に実現する構文です。Promiseが解決されるまでの間に「待機中の表示」を行い、解決された場合にはその結果を表示し、エラーが発生した場合にはエラーメッセージを表示します。

<script lang="ts">
  // 指定ms後にrejectするPromise
  function sleepNg(ms: number) {
    return new Promise((_, reject) => setTimeout(() => reject('reject!'), ms));
  }
</script>

<h1>AwaitBlock</h1>
<!-- 2秒後に表示 -->
{#await sleepNg(2000)}
  <p>waiting...</p>
{:then message}
  <p>{message}</p>
{:catch error}
  <p>{error}</p>
{/await}

{#await プロミス then 結果}で待機画面のない簡易的な表現

{#await}ブロックは、{:then}{:catch}を使わずに、簡易的に記述することもできます。この場合、待機画面を省略し、Promiseが解決されたときの表示だけを記述できます。

{#await}の後にthenキーワードを使うことで、待機中の表示を省略できます。Promiseが解決された場合のみ表示を更新します。

<script lang="ts">
  // 指定ms後にresolveするPromise
  function sleepOk(ms: number) {
    return new Promise((resolve) => setTimeout(() => resolve('resolve!'), ms));
  }
</script>

<h1>AwaitBlock</h1>
<!-- 3秒後に表示 -->
{#await sleepOk(3000) then message}
  <p>{message}</p>
{/await}

AwaitBlock.svelte

<script lang="ts">
  // 指定ms後にresolveするPromise
  function sleepOk(ms: number) {
    return new Promise((resolve) => setTimeout(() => resolve('resolve!'), ms));
  }

  // 指定ms後にrejectするPromise
  function sleepNg(ms: number) {
    return new Promise((_, reject) => setTimeout(() => reject('reject!'), ms));
  }
</script>

<h1>AwaitBlock</h1>
<!-- 2秒後に表示 -->
{#await sleepNg(2000)}
  <p>waiting...</p>
{:then message}
  <p>{message}</p>
{:catch error}
  <p>{error}</p>
{/await}

<!-- 3秒後に表示 -->
{#await sleepOk(3000) then message}
  <p>{message}</p>
{/await}

参考リンク集

イベント

概要

  • onイベント名={関数}でイベント処理
  • 親コンポーネントから関数をpropsで渡す
  • v4で使えた修飾子は廃止

onイベント名={関数}でイベント処理

イベント名を直接属性として指定しイベントハンドラを指定します。属性として渡すので属性名と関数名(変数名)が一致する場合の省略表現を使えます。属性値として直接関数を記述することもできます。

従来のon:ディレクティブは非推奨となりました。

<script lang="ts">
  // イベントハンドラ
  function clickHandle(event: Event) {
    if (event.target instanceof HTMLButtonElement) {
      alert(event.target.innerHTML);
    }
  }

  // イベントと同名の関数を定義することで省略表現でイベントを設定できる
  function onclick(event: Event) {
    if (event.target instanceof HTMLButtonElement) {
      alert(event.target.innerHTML);
    }
  }
</script>

<h1>Events</h1>
<div>
  <h3>DOMイベント</h3>
  <!-- イベントハンドラを指定 -->
  <button onclick={clickHandle}>アラート(関数名指定で設定)</button>
  <!-- イベントと同名のハンドラを指定 -->
  <button {onclick}>アラート(省略表現で設定)</button>
  <!-- 処理を直接記述 -->
  <button
    onclick={(event: Event) => {
      alert((event.target as HTMLButtonElement).innerHTML);
    }}>アラート(関数直接指定で設定)</button
  >
</div>

親コンポーネントから関数をpropsで渡す

v5では、親コンポーネントから子コンポーネントに関数をpropsとして渡し、子コンポーネント内でその関数を呼び出すことで、親から子へのデータ伝達やイベント処理を行います。

<script lang="ts">
  import Events from './components/Events.svelte';
  // Evemtsコンポーネントで使用する関数
  let isOk = $state(true);
  function parentHandle() {
    isOk = !isOk;
  }
</script>

<Events {parentHandle} />
{#if isOk}
  <p>OK</p>
{:else}
  <p>NG</p>
{/if}

<script lang="ts">
  // 親コンポーネントから渡された関数(イベント処理で利用)
  const { parentHandle } = $props();
</script>

<h1>Events</h1>
<div>
  <h3>コンポーネントイベント</h3>
  <button onclick={parentHandle}>親からもらった関数で処理</button>
</div>

v4で使えた修飾子は廃止

v5では、従来のイベント修飾子(例:|preventDefault|stopPropagation)が廃止されました。これにより、イベントの制御はイベントハンドラ内で明示的に行う必要があります。

App.svelte(親)

<script lang="ts">
  import Events from './components/Events.svelte';
  // Evemtsコンポーネントで使用する関数
  let isOk = $state(true);
  function parentHandle() {
    isOk = !isOk;
  }
</script>

<Events {parentHandle} />
{#if isOk}
  <p>OK</p>
{:else}
  <p>NG</p>
{/if}

Events.svelte(子)

<script lang="ts">
  // イベントハンドラ
  function clickHandle(event: Event) {
    if (event.target instanceof HTMLButtonElement) {
      alert(event.target.innerHTML);
    }
  }

  // イベントと同名の関数を定義することで省略表現でイベントを設定できる
  function onclick(event: Event) {
    if (event.target instanceof HTMLButtonElement) {
      alert(event.target.innerHTML);
    }
  }

  // 親コンポーネントから渡された関数(イベント処理で利用)
  const { parentHandle } = $props();
</script>

<h1>Events</h1>
<div>
  <h3>DOMイベント</h3>
  <!-- イベントハンドラを指定 -->
  <button onclick={clickHandle}>アラート(関数名指定で設定)</button>
  <!-- イベントと同名のハンドラを指定 -->
  <button {onclick}>アラート(省略表現で設定)</button>
  <!-- 処理を直接記述 -->
  <button
    onclick={(event: Event) => {
      alert((event.target as HTMLButtonElement).innerHTML);
    }}>アラート(関数直接指定で設定)</button
  >
</div>

<div>
  <h3>コンポーネントイベント</h3>
  <button onclick={parentHandle}>親からもらった関数で処理</button>
</div>

参考リンク集
v4の書き方

サンプルは上記と同等の内容を再現しています。

on:ディレクティブでイベントをリスニング

v4では、on:ディレクティブを使って、イベントをリスニングすることができます。on:に続けてイベント名を指定します。

createEventDispatcher関数でコンポーネントイベント

v4ではcreateEventDispatcher関数を使うことで、子コンポーネントから親コンポーネントにカスタムイベントを発火し、親側でそれをリスニングすることができます。親コンポーネントでon:イベント名ディレクティブを使ってイベントをリスニングします。

on:ディレクティブの修飾子

v4では、on:ディレクティブを使ってイベントをリスニングする際に「修飾子」を使用することで、追加の動作を指定できます。修飾子を使うと、イベントの発生タイミングや処理方法を簡単にカスタマイズできます。

App.svelte(親)

<script lang="ts">
  import EventsV4 from './components/EventsV4.svelte';
  // Evemtsコンポーネントで使用する関数
  let isOk = $state(true);
  function parentHandle() {
    isOk = !isOk;
  }
</script>

<EventsV4 on:childevent={parentHandle} />
{#if isOk}
  <p>OK</p>
{:else}
  <p>NG</p>
{/if}

EventsV4.svelte

<script lang="ts">
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();

  // イベントハンドラ
  function clickHandle(event: Event) {
    if (event.target instanceof HTMLButtonElement) {
      alert(event.target.innerHTML);
    }
  }

  // イベントと同名の関数を定義することで省略表現でイベントを設定できる
  function onclick(event: Event) {
    if (event.target instanceof HTMLButtonElement) {
      alert(event.target.innerHTML);
    }
  }
</script>

<h1>Events</h1>
<div>
  <h3>DOMイベント</h3>
  <!-- イベントハンドラを指定 -->
  <button on:click={clickHandle}>アラート(関数名指定で設定)</button>
  <!-- イベントと同名のハンドラを指定 -->
  <button on:click={onclick}>アラート(省略表現で設定)</button>
  <!-- 処理を直接記述 -->
  <button
    on:click={(event: Event) => {
      if (event.target instanceof HTMLButtonElement) {
        alert(event.target.innerHTML);
      }
    }}>アラート(関数直接指定で設定)</button
  >
</div>

<div>
  <h3>コンポーネントイベント</h3>
  <button on:click={()=>dispatch('childevent')}>子側でコンポーネントイベントを発生</button>
</div>

https://learn.svelte.jp/tutorial/dom-events

https://learn.svelte.jp/tutorial/event-modifiers

https://learn.svelte.jp/tutorial/component-events

https://learn.svelte.jp/tutorial/event-forwarding

https://learn.svelte.jp/tutorial/dom-event-forwarding

入力の受け取り

概要

  • bind:value ディレクティブで入力欄とstateを紐付け
  • type="number" は数値型で受け取り可能

bind:value ディレクティブで入力欄とstateを紐付け

bind:valueディレクティブを使用して、入力フィールドとコンポーネントの状態(state)を双方向にバインドできます。これにより、ユーザーの入力と状態が常に同期され、リアクティブなインターフェースを簡単に構築できます。

type="number" は数値型で受け取り可能

<input>要素のtype属性を"number"に設定し、bind:valueディレクティブを使用することで、ユーザーの入力を数値として直接受け取ることができます。

InputVal.svelte

<script lang="ts">
  //  入力欄と紐付けるstate
  let inputValue = $state('');
  let num = $state(0);
</script>

<h1>InputVal</h1>
<!-- bind:valueでstateと紐付け -->
<input bind:value={inputValue} />
<p>入力値: {inputValue}</p>

<!-- type="number"で数値型として受け取り -->
<input type="number" bind:value={num} />
<p>{typeof num}: {num}</p>

参考リンク集

状態管理

概要

  • ContextAPIでコンポーネント間で共有
  • 共有するデータはオブジェクトとする
  • xxx.svelte.tsファイルで$stateを使う関数を定義

ContextAPIでコンポーネント間で共有

Context APIを使用すると、コンポーネント間でデータや関数を直接渡すことなく、親から子孫コンポーネントに値を共有できます。

親コンポーネントでsetContext(キー, 値)を使用して値を設定し、子コンポーネントでgetContext(キー)を使用してその値を取得します。

setContextgetContextは型パラメータで型を指定することができます。指定しない場合はunknown型として扱われます。

<script lang="ts">
  // コンテキストに値をセットするための関数
  import { setContext } from 'svelte';
  import StateMgtChild1 from './StateMgtChild1.svelte';

  // コンテキストで共有するデータをセット
  setContext('title', 'title is ABC');
</script>

<h1>State Management Parent</h1>
<!-- 子コンポーネントでしかコンテキストの値を取り出せない -->
<StateMgtChild1 />
<StateMgtChild2 />

<script lang="ts">
  // コンテキストから値を取り出すための関数
  import { getContext } from 'svelte';

  // コンテキストで共有されてるデータを取り出す
  // 型パラメータで型を指定していないのでunknown型
  const title = getContext('title');
</script>

<h1>State Management Child1</h1>
{title}

共有するデータはオブジェクトとする

Context API自体はリアクティブではありません。v5では、$stateを含むオブジェクトをコンポーネント間で共有することで、状態の変更をコンポーネント間でリアクティブに反映させることが可能です。

<script lang="ts">
  // コンテキストに値をセットするための関数
  import { setContext } from 'svelte';
  import StateMgtChild1 from './StateMgtChild1.svelte';
  import StateMgtChild2 from './StateMgtChild2.svelte';
  // コンテキストで共有するデータの型
  import type { Counter } from '../models/Counter';

  let count = $state({num:0});
  const addCount = () => (count.num += 1);

  // 型パラメータで型を指定することもできる
  setContext<Counter>('count', { count, addCount });
</script>

<h1>State Management Parent</h1>
<!-- 子コンポーネントでしかコンテキストの値を取り出せない -->
<StateMgtChild1 />
<StateMgtChild2 />

<script lang="ts">
  // コンテキストから値を取り出すための関数
  import { getContext } from 'svelte';
  // コンテキストで共有されてるデータの型
  import type { Counter } from '../models/Counter';

  // コンテキストから値を取り出す
  // 型パラメータで型を指定しないとunknown型になる
  const { count, addCount } = getContext<Counter>('count');
</script>

<h1>State Management Child1</h1>
{count.num}
<!-- 操作がコンポーネントを跨いでリアクティブに連動する -->
<button onclick={addCount}>++</button>

xxx.svelte.tsファイルで$stateを使う関数を定義

v5では、$stateルーンを使用してリアクティブな状態を管理できます。これを.svelte.tsファイル内で関数として定義し、他のコンポーネントから利用することで、状態管理の複雑さを隠蔽し、コードの再利用性と可読性を向上させることができます。

直接コンポーネント内に記述することもできますが、コンポーネント内に複雑なロジックが増えると可読性が下がるため、分離して記述することもできます。

xxx.svelte.ts

import type { Counter } from "../models/Counter";

// コンテキストに保存するデータを用意する関数
// stateと操作の関数を返す
export function useCounter(): Counter {
  let count = $state({ num: 0 });
  const addCount = () => {
    count.num += 1;
  }
  return { count, addCount };
}

コンポーネント

<script lang="ts">
  // コンテキストに値をセットするための関数
  import { setContext } from 'svelte';
  import StateMgtChild1 from './StateMgtChild1.svelte';
  import StateMgtChild2 from './StateMgtChild2.svelte';
  // コンテキストで共有するデータの型
  import type { Counter } from '../models/Counter';
  // コンテキストで共有するデータを定義した関数
  import { useCounter } from '../store/useCounter.svelte';

  // useCounter.svelte.tsに移動
  // let count = $state({num:0});
  // const addCount = () => (count.num += 1);
  // setContext<Counter>('count', { count, addCount });

  // 型パラメータで型を指定することもできる
  setContext<Counter>('count', useCounter());
</script>

<h1>State Management Parent</h1>
<!-- 子コンポーネントでしかコンテキストの値を取り出せない -->
<StateMgtChild1 />
<StateMgtChild2 />

StateMgtParent.svelte(親:setContextで値を共有)

<script lang="ts">
  // コンテキストに値をセットするための関数
  import { setContext } from 'svelte';
  import StateMgtChild1 from './StateMgtChild1.svelte';
  import StateMgtChild2 from './StateMgtChild2.svelte';
  // コンテキストで共有するデータの型
  import type { Counter } from '../models/Counter';
  // コンテキストで共有するデータを定義した関数
  import { useCounter } from '../store/useCounter.svelte';

  // useCounter.svelte.tsに移動
  // let count = $state({num:0});
  // const addCount = () => (count.num += 1);
  // setContext<Counter>('count', { count, addCount });

  // コンテキストで共有するデータをセット
  setContext('title', 'title is ABC');
  // 型パラメータで型を指定することもできる
  setContext<Counter>('count', useCounter());
</script>

<h1>State Management Parent</h1>
<!-- 子コンポーネントでしかコンテキストの値を取り出せない -->
<StateMgtChild1 />
<StateMgtChild2 />

StateMgtChild1.svelte(子:getContextで値を取得)

<script lang="ts">
  // コンテキストから値を取り出すための関数
  import { getContext } from 'svelte';
  // コンテキストで共有されてるデータの型
  import type { Counter } from '../models/Counter';

  // コンテキストから値を取り出す
  // 型パラメータで型を指定しないとunknown型になる
  const { count, addCount } = getContext<Counter>('count');
  // コンテキストで共有されてるデータを取り出す
  // 型パラメータで型を指定していないのでunknown型
  const title = getContext('title');
</script>

<h1>State Management Child1</h1>
{title}
{count.num}
<!-- 操作がコンポーネントを跨いでリアクティブに連動する -->
<button onclick={addCount}>++</button>

useCounter.svelte.ts(共有するデータを用意する関数)

import type { Counter } from "../models/Counter";

// コンテキストに保存するデータを用意する関数
// stateと操作の関数を返す
export function useCounter(): Counter {
  let count = $state({ num: 0 });
  const addCount = () => {
    count.num += 1;
  }
  return { count, addCount };
}

Counter.ts(共有するデータの型を定義したインターフェース)

// コンテキストに保存するデータの型を定義
export interface Counter { 
    count: { num: number };
    addCount: () => void;
};

参考リンク集
v4の書き方

サンプルは上記と同等の内容を再現しています。

v5では、ルーンの導入により、コンポーネント内での状態管理がより直感的かつ簡潔になりました。これに伴い、従来のストアを使用する場合のデメリットが明確になってきましたでルーンが導入される前は状態の共有でストアが利用されていました。現在ではルーンでも同様のことができます。

writableストアをエクスポートして共有

writableストアを使用すると、複数のコンポーネント間で状態を簡単に共有できます。

$ストアで値を参照

ストアの値を直接利用する際に、変数名の前に$を付けることで、自動的にsubscribeし、値の変更に応じてコンポーネントが再レンダリングされます。再代入で値を更新することができます。

ストアの学習のハードル

ストアは特に初心者にとっては習得に時間がかかることがあります。Svelteにはwritablereadablederivedなど複数のストアが存在し、それぞれの用途や使い方を正しく理解し、適切に使い分ける必要があります。独自のロジックを持つカスタムストアを作成する際、subscribesetupdateなどのメソッドの正しい使い方や、再現性のある状態管理を実装するための設計が求められます。

StateMgtParentV4.svelte

<script lang="ts">
  import StateMgtChild1V4 from './StateMgtChild1V4.svelte';
  import { countStore } from '../store/useCounterStore';
</script>

<h1>State Management Parent</h1>
{$countStore}
<StateMgtChild1V4 />

StateMgtChild1V4.svelte

<script lang="ts">
  import { countStore } from '../store/useCounterStore';
</script>

<h1>State Management Child1</h1>
{$countStore}
<!-- 操作がコンポーネントを跨いでリアクティブに連動する -->
<button onclick={() => ($countStore += 1)}>++</button>

useCounterStore.ts

import { writable } from "svelte/store";

// Storeを作成して外部に公開
export const countStore = writable(0);

v4

https://learn.svelte.jp/tutorial/writable-stores

画像の取り込み

概要

  • assetsフォルダに格納する方法
  • publicフォルダに格納する方法

assetsフォルダに格納する方法

プロジェクト内にassetsフォルダを作成し、その中に画像を配置します。Viteの機能を活用して画像をインポートし、コンポーネント内で使用できます。ビルド時に含まれます。

publicフォルダに格納する方法

publicフォルダに画像を配置することもできます。ネットワーク経由で取得するのと同じです。

ImageView.svelte

<script lang="ts">
  let src = './dog.jpg'; // publicフォルダ
  import src2 from '../assets/dog.jpg'; // assetsフォルダ
</script>

<img {src} alt="dog" height="50px" />
<img src={src2} alt="dog" height="50px" />

おわりに

Svelteの記述はルーンによりシンプルになりました。その一方で出たての表現であり以前の書き方も残っているため、しばらくは両方の知識が必要になるかもしれません。

これまでのSvelteは既存のキーワードで様々な表現をしており、JavaScriptとの見分けがつきづらい部分がありましたが、ルーンで少し分かりやすくなった。。。?

1

Discussion

ログインするとコメントできます