Svelte入門:リファレンス $app/state

スポンサーリンク
Svelte入門:リファレンス $app/state 用語解説
Svelte入門:リファレンス $app/state
この記事は約23分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

今日は、SvelteKitのリファレンスについて解説しています。

スポンサーリンク

背景

SvelteKitのリファレンスについて調査する機会がありましたので、その時の内容を備忘として記事に残しました。

$app/state

SvelteKit は $app/state モジュールを通じて3つの読み取り専用の状態オブジェクト — pagenavigatingupdated — を利用可能にしています。 このモジュールはバージョン 2.12 で追加されました。それより前のバージョンの SvelteKit を使用している場合は、代わりに $app/stores を使用してください。

import { navigating, page, updated } from '$app/state';

$app/state モジュールは SvelteKit アプリケーションでナビゲーションや更新状態に関する情報にアクセスするための重要なユーティリティを提供します。このモジュールには以下の3つの主要なエクスポートが含まれています:

  1. page – 現在のページに関する情報(URL、パラメータ、ロードデータなど)を含むオブジェクトです。
  2. navigating – ページ間のナビゲーションが進行中の場合に情報を提供し、ナビゲーションがない場合は null となります。ローディングインジケータやトランジションの実装に役立ちます。
  3. updated – アプリケーションの新しいバージョンが利用可能になった場合に true となるプロパティです。これを使用して、ユーザーにアップデートを通知できます。

これらはすべて読み取り専用の状態オブジェクトであり、アプリケーション全体で現在の状態に関する一貫した情報を提供します。

バージョン 2.12 より前の SvelteKit では、同様の機能が $app/stores モジュールを通じて提供されていましたが、現在は $app/state の使用が推奨されています。

次のセクションで、各状態オブジェクトの詳細な説明を行います。

navigating

進行中のナビゲーションを表す読み取り専用オブジェクトで、fromtotype、および(type === 'popstate' の場合)delta プロパティを持ちます。ナビゲーションが発生していない場合やサーバーレンダリング中は値が null になります。

const navigating:
	| import('@sveltejs/kit').Navigation
	| {
			from: null;
			to: null;
			type: null;
			willUnload: null;
			delta: null;
			complete: null;
	  };

解説

navigating は SvelteKit の $app/state モジュールが提供する状態オブジェクトで、以下のような特徴があります:

  1. ナビゲーション状態の追跡 – アプリケーション内でのナビゲーションの進行状況を追跡します。
  2. プロパティ:
    • from – ナビゲーション元のページに関する情報(URL、パラメータなど)
    • to – ナビゲーション先のページに関する情報
    • type – ナビゲーションのタイプ(linkpopstategoto など)
    • deltatype === 'popstate'(ブラウザの戻る/進むボタンによるナビゲーション)の場合の履歴スタックの変化量
    • willUnload – ナビゲーションがページのアンロードを引き起こすかどうかを示すブール値
    • complete – ナビゲーションの完了を示す Promise
  3. null 状態 – ナビゲーションが進行中でない場合や、サーバーサイドレンダリング中は、すべてのプロパティが null になります。

使用例

<script>
  import { navigating } from '$app/state';
  
  // ナビゲーション中かどうかを表す計算プロパティ
  $: isNavigating = $navigating !== null;
  
  // ナビゲーションタイプに基づく条件付きロジック
  $: {
    if ($navigating) {
      console.log(`ナビゲーションタイプ: ${$navigating.type}`);
      console.log(`移動元: ${$navigating.from.url.pathname}`);
      console.log(`移動先: ${$navigating.to.url.pathname}`);
      
      if ($navigating.type === 'popstate') {
        console.log(`履歴の移動量: ${$navigating.delta}`);
      }
    }
  }
</script>

<!-- ナビゲーション中にローディングインジケータを表示 -->
{#if isNavigating}
  <div class="loading-indicator">
    <svg class="spinner" viewBox="0 0 50 50">
      <circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
    </svg>
    <p>ページ読み込み中...</p>
    <p>
      {$navigating.from.url.pathname} から {$navigating.to.url.pathname} へ移動しています
    </p>
  </div>
{/if}

<!-- ナビゲーションタイプに基づいたトランジション -->
<div class={$navigating ? `transition-${$navigating.type}` : ''}>
  <!-- ページコンテンツ -->
  <slot />
</div>

<style>
  .loading-indicator {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    background: rgba(255, 255, 255, 0.8);
    padding: 1rem;
    text-align: center;
    z-index: 1000;
  }
  
  .spinner {
    width: 50px;
    height: 50px;
    animation: rotate 2s linear infinite;
  }
  
  .path {
    stroke: #007bff;
    stroke-linecap: round;
    animation: dash 1.5s ease-in-out infinite;
  }
  
  @keyframes rotate {
    100% {
      transform: rotate(360deg);
    }
  }
  
  @keyframes dash {
    0% {
      stroke-dasharray: 1, 150;
      stroke-dashoffset: 0;
    }
    50% {
      stroke-dasharray: 90, 150;
      stroke-dashoffset: -35;
    }
    100% {
      stroke-dasharray: 90, 150;
      stroke-dashoffset: -124;
    }
  }
  
  /* ナビゲーションタイプに基づいたトランジションスタイル */
  .transition-link {
    animation: fade 0.3s;
  }
  
  .transition-popstate {
    animation: slide 0.3s;
  }
  
  @keyframes fade {
    from { opacity: 0.5; }
    to { opacity: 1; }
  }
  
  @keyframes slide {
    from { transform: translateX(10px); }
    to { transform: translateX(0); }
  }
</style>

navigating オブジェクトは、以下のようなシナリオで特に役立ちます:

  1. ローディングインジケータ – ページナビゲーション中にローダーやプログレスバーを表示する。
  2. トランジションアニメーション – ナビゲーションのタイプ(リンククリック、履歴ナビゲーションなど)に基づいて異なるトランジションを適用する。
  3. フォーム送信の防止 – ナビゲーション中に追加のフォーム送信を防止する。
  4. ナビゲーション追跡 – アナリティクスのためにナビゲーションを追跡する。
  5. 条件付きレンダリング – ナビゲーション中に特定のUI要素を表示または非表示にする。

navigating オブジェクトを使用することで、よりスムーズで洗練されたユーザー体験を提供するインタラクションを実装できます。

page

現在のページに関する情報を持つ読み取り専用のリアクティブオブジェクトで、以下のようなユースケースに役立ちます:

  • コンポーネントツリーのどこからでも、すべてのページ/レイアウトの組み合わされた data を取得する(データの読み込みも参照)
  • コンポーネントツリーのどこからでも、form プロパティの現在の値を取得する(フォームアクションも参照)
  • gotopushState、または replaceState を通じて設定されたページ状態を取得する(goto とシャロールーティングも参照)
  • 現在いる URL、現在のルートとそのパラメータ、エラーが発生したかどうかなどのメタデータを取得する

+layout の例

<script lang="ts">
	import { page } from '$app/state';
</script>

<p>現在 {page.url.pathname} にいます</p>

{#if page.error}
	<span class="red">問題が検出されました</span>
{:else}
	<span class="small">すべてのシステムは正常に動作しています</span>
{/if}

page の変更は、ルーンを使用した場合にのみ利用可能です。(レガシーなリアクティビティ構文では変更が反映されません)

+page の例

<script lang="ts">
	import { page } from '$app/state';
	const id = $derived(page.params.id); // これはこのページで使用する id を正しく更新します
	$: badId = page.params.id; // 使用しないでください。初期ロード後に更新されません
</script>

サーバー上では、レンダリング中にのみ値を読み取ることができます(つまり、load 関数などでは読み取れません)。ブラウザでは、いつでも値を読み取ることができます。

const page: import('@sveltejs/kit').Page;

解説

page は SvelteKit の $app/state モジュールが提供する状態オブジェクトで、以下のような特徴があります:

  1. 現在のページ情報 – URL、パラメータ、データ、エラー状態などの現在のページに関する情報を提供します。
  2. リアクティブな値page の値はリアクティブであり、ナビゲーションに応じて自動的に更新されます。
  3. 主なプロパティ:
    • page.url – 現在のページの URL 情報(pathnamesearchhash など)
    • page.params – ルートパラメータ(例: /blog/[slug]slug 値)
    • page.route – 現在のルート ID
    • page.status – HTTP ステータスコード
    • page.error – エラーが発生した場合、そのエラーオブジェクト
    • page.data – すべてのページ/レイアウト load 関数から返されたデータの組み合わせ
    • page.form – フォームアクションから返されたデータ
    • page.stategotopushState、または replaceState を通じて設定された状態
  4. ルーンとの互換性 – SvelteKit 2 では、page の変更はルーン構文($derived など)を使用した場合にのみ正しく反映されます。従来の Svelte のリアクティビティ構文($:)では、初期値以降の変更が反映されません。
  5. サーバーサイドの制限 – サーバーサイドでは、レンダリング中(コンポーネント内)でのみ値にアクセスでき、load 関数などでは利用できません。

使用例

<script>
  import { page } from '$app/state';
  
  // ルーンを使ったリアクティブな値の取得
  const pathname = $derived(page.url.pathname);
  const slug = $derived(page.params.slug);
  const userData = $derived(page.data.user);
  const formData = $derived(page.form);
  
  // 現在のルートに基づいた条件付きロジック
  const isHomePage = $derived(page.url.pathname === '/');
  const isBlogPost = $derived(page.route.id?.startsWith('/blog/'));
  
  // エラーハンドリング
  const hasError = $derived(page.error !== null);
  const errorMessage = $derived(page.error?.message ?? '');
</script>

<!-- 現在のページ情報の表示 -->
<header>
  <h1>現在のページ: {pathname}</h1>
  {#if page.params.slug}
    <h2>スラグ: {page.params.slug}</h2>
  {/if}
</header>

<!-- データの使用 -->
{#if userData}
  <div class="user-profile">
    <img src={userData.avatar} alt="プロフィール画像" />
    <h3>{userData.name}</h3>
    <p>{userData.bio}</p>
  </div>
{/if}

<!-- ナビゲーション状態に基づいたUI -->
<nav>
  <ul>
    <li class:active={isHomePage}><a href="/">ホーム</a></li>
    <li class:active={pathname === '/about'}><a href="/about">会社概要</a></li>
    <li class:active={pathname === '/contact'}><a href="/contact">お問い合わせ</a></li>
    <li class:active={isBlogPost}><a href="/blog">ブログ</a></li>
  </ul>
</nav>

<!-- エラー表示 -->
{#if hasError}
  <div class="error-container">
    <h2>エラーが発生しました</h2>
    <p>{errorMessage}</p>
    <p>ステータスコード: {page.status}</p>
  </div>
{/if}

<!-- フォームデータの表示 -->
{#if formData}
  <div class="form-results">
    <h3>送信結果:</h3>
    <pre>{JSON.stringify(formData, null, 2)}</pre>
  </div>
{/if}

<style>
  .active {
    font-weight: bold;
    text-decoration: underline;
  }
  
  .error-container {
    border: 2px solid red;
    padding: 1rem;
    margin: 1rem 0;
    background-color: #ffeeee;
  }
  
  .user-profile {
    border: 1px solid #ccc;
    padding: 1rem;
    margin: 1rem 0;
  }
</style>

page オブジェクトは、以下のようなシナリオで特に役立ちます:

  1. 条件付きレンダリング – 現在のルートやパラメータに基づいて UI を条件付きでレンダリングする。
  2. ナビゲーション状態の反映 – 現在のパスに基づいてナビゲーションメニューでアクティブなアイテムをハイライト表示する。
  3. データアクセス – コンポーネントツリーのどこからでも、load 関数から返されたデータにアクセスする。
  4. フォームフィードバック – フォームアクションの結果を表示する。
  5. エラー処理 – カスタムエラー UI を表示する。
  6. 動的メタデータ – 現在のページに基づいてタイトルやメタタグを設定する。

SvelteKit 2 を使用している場合は、page オブジェクトの変更を正しく追跡するために、ルーン構文($derived など)を使用することが重要です。

updated

初期値が false の読み取り専用リアクティブ値です。version.pollInterval が非ゼロ値の場合、SvelteKit はアプリケーションの新しいバージョンをポーリングで確認し、新バージョンを検出すると currenttrue に更新します。updated.check() はポーリングに関係なく、即時チェックを強制します。

const updated: {
	get current(): boolean;
	check(): Promise<boolean>;
};

解説

updated は SvelteKit の $app/state モジュールが提供する状態オブジェクトで、以下のような特徴があります:

  1. アプリケーション更新の検出 – アプリケーションの新しいバージョンが利用可能になったかどうかを追跡します。
  2. プロパティとメソッド:
    • current – 新しいバージョンが利用可能な場合に true となる読み取り専用のブール値
    • check() – 新しいバージョンを即時にチェックするメソッド。新しいバージョンが利用可能な場合は true を解決する Promise を返します
  3. 設定との関連 – この機能は version.pollInterval が設定されている場合にのみ機能します。これは svelte.config.js で設定できます。
  4. ユースケース – ユーザーに新しいバージョンが利用可能であることを通知し、アプリケーションを更新するオプションを提供するために使用されます。

使用例

<script>
  import { updated } from '$app/state';
  
  // 更新が利用可能かどうかをチェックする関数
  async function checkForUpdates() {
    const hasUpdate = await updated.check();
    if (hasUpdate) {
      console.log('新しいバージョンが利用可能です');
    } else {
      console.log('アプリケーションは最新です');
    }
  }
  
  // コンポーネントがマウントされたときに更新をチェック
  import { onMount } from 'svelte';
  
  onMount(() => {
    // 初回チェック
    checkForUpdates();
  });
</script>

<!-- 更新通知バナー -->
{#if $updated.current}
  <div class="update-banner">
    <p>新しいバージョンが利用可能です!</p>
    <button on:click={() => location.reload()}>
      更新する
    </button>
  </div>
{/if}

<!-- 手動更新チェックボタン -->
<button on:click={checkForUpdates}>
  更新を確認
</button>

<style>
  .update-banner {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background: #4caf50;
    color: white;
    padding: 1rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    z-index: 1000;
  }
  
  button {
    background: white;
    color: #4caf50;
    border: none;
    padding: 0.5rem 1rem;
    border-radius: 4px;
    cursor: pointer;
    font-weight: bold;
  }
  
  button:hover {
    background: #f0f0f0;
  }
</style>

設定例(svelte.config.js):

// svelte.config.js
import adapter from '@sveltejs/adapter-auto';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter(),
    version: {
      // 1分(60,000ミリ秒)ごとに更新をチェック
      pollInterval: 60000
    }
  }
};

export default config;

アプリケーション全体での使用例(+layout.svelte):

<script>
  import { updated } from '$app/state';
  import { fade } from 'svelte/transition';
  
  let dismissedUpdate = false;
  
  function dismissUpdate() {
    dismissedUpdate = true;
  }
  
  function refreshApp() {
    location.reload();
  }
</script>

<slot />

<!-- 更新通知 -->
{#if $updated.current && !dismissedUpdate}
  <div class="update-notification" transition:fade={{ duration: 300 }}>
    <div class="content">
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2">
        <path d="M23 12l-3.36-.83L21 8l-3.2-1.6L17 3l-3.22 1.61L10.56 3 8.5 7l-3.2 1.6 1.36 3.17L3 12l3.36.83L5 16l3.2 1.6L10 22l3.22-1.61L16.44 22l2.06-4 3.2-1.6-1.36-3.17L23 12z"/>
      </svg>
      <p>アプリケーションの新しいバージョンが利用可能です</p>
    </div>
    <div class="actions">
      <button class="dismiss" on:click={dismissUpdate}>
        後で
      </button>
      <button class="update" on:click={refreshApp}>
        今すぐ更新
      </button>
    </div>
  </div>
{/if}

<style>
  .update-notification {
    position: fixed;
    bottom: 20px;
    right: 20px;
    background: white;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    border-radius: 8px;
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    max-width: 320px;
    z-index: 1000;
  }
  
  .content {
    display: flex;
    align-items: center;
    gap: 12px;
  }
  
  .content svg {
    color: #4caf50;
    flex-shrink: 0;
  }
  
  .actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
  }
  
  button {
    padding: 8px 16px;
    border-radius: 4px;
    font-weight: 500;
    cursor: pointer;
    border: none;
  }
  
  .dismiss {
    background: transparent;
    color: #666;
  }
  
  .update {
    background: #4caf50;
    color: white;
  }
  
  .dismiss:hover {
    background: #f0f0f0;
  }
  
  .update:hover {
    background: #43a047;
  }
</style>

updated オブジェクトは、以下のようなシナリオで特に役立ちます:

  1. アプリケーション更新通知 – ユーザーに新しいバージョンが利用可能であることを通知し、更新オプションを提供する。
  2. 強制更新 – セキュリティ修正など、重要な更新がある場合に、ユーザーに更新を促す。
  3. バックグラウンド更新 – ユーザーの邪魔をせずに、バックグラウンドでアプリケーションを更新する。
  4. 更新スケジュール – 特定の条件(例:アイドル時間中)に基づいて更新を提案する。

SvelteKit のこの機能を使用することで、ユーザーに常に最新バージョンのアプリケーションを使用してもらうことができ、バグ修正やセキュリティ更新が確実にすべてのユーザーに届くようになります。

おわりに

今日は、 SvelteKitのリファレンスについて解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました