AtCoderNoviSteps icon indicating copy to clipboard operation
AtCoderNoviSteps copied to clipboard

[Feature] 問題集のURLでカリキュラム別と解法別を追加できるようにしましょう

Open KATO-Hiro opened this issue 11 months ago • 2 comments

Description / 説明

  • A clear and concise description of what you want to happen.

Motivation / 動機

  • 問題集の種類別にデータおよびUI を明示的に分けるため

Other notes / その他

  • Add any other context or screenshots about the feature request here.
  • Will you try to create a pull request?
    • yes / no

See

https://svelte.dev/tutorial/kit/breaking-out-of-layouts

KATO-Hiro avatar Feb 26 '25 08:02 KATO-Hiro

SvelteKitのルーティングシステムでは、同じページで複数のルートの内容を表示することは可能です。これを実現するには、いくつかのアプローチがあります。

1. ネストされたレイアウトを使用する方法

SvelteKitでは、ネストされたレイアウトを使用して、親ルートの内容を保持しながら子ルートの内容を追加表示することができます。

src/
  routes/
    hoge/
      +page.svelte  // /hogeのコンテンツ
      +layout.svelte  // レイアウトファイル
      foo/
        +page.svelte  // /hoge/fooのコンテンツ

/hoge/+layout.svelte

<div>
  <h1>Hogeページの共通コンテンツ</h1>
  
  <!-- 現在のページのコンテンツをレンダリング -->
  <slot />
  
  <!-- 追加のコンテンツエリア -->
  <div id="additional-content">
    {#if $page.url.pathname === '/hoge/foo'}
      <!-- fooページの内容を表示する場合の処理 -->
      <svelte:component this={import('./foo/+page.svelte')} />
    {/if}
  </div>
</div>

2. ページパラメータを使用する方法

クエリパラメータやURLパラメータを使用して、同じページ内でコンテンツを動的に追加表示することもできます。

<script>
  import { page } from '$app/stores';
  import { onMount } from 'svelte';
  import FooContent from './foo/+page.svelte';
  
  let showFooContent = false;
  
  $: {
    // URLが/hoge/fooの場合、fooのコンテンツも表示する
    if ($page.url.pathname === '/hoge/foo') {
      showFooContent = true;
    }
  }
</script>

<div>
  <h1>Hogeページのメインコンテンツ</h1>
  
  <!-- fooへのリンク -->
  <a href="/hoge/foo">Fooコンテンツを表示</a>
  
  <!-- fooのコンテンツ(条件付き表示) -->
  {#if showFooContent}
    <div class="foo-content">
      <FooContent />
    </div>
  {/if}
</div>

3. SvelteKitのロードファンクションを使用する方法

+page.jsまたは+page.server.jsでデータをロードし、それをページコンポーネントで表示することもできます。

/hoge/+page.js:

export async function load({ url }) {
  const showFoo = url.pathname === '/hoge/foo';
  
  let fooData = null;
  if (showFoo) {
    // fooに関連するデータを取得する処理
    fooData = await fetchFooData();
  }
  
  return {
    showFoo,
    fooData
  };
}

/hoge/+page.svelte:

<script>
  export let data;
</script>

<div>
  <h1>Hogeページのメインコンテンツ</h1>
  
  <a href="/hoge/foo">Fooコンテンツを表示</a>
  
  {#if data.showFoo}
    <div class="foo-content">
      <!-- fooのコンテンツ -->
      <h2>Fooのコンテンツ</h2>
      <p>{data.fooData}</p>
    </div>
  {/if}
</div>

いずれの方法でも、URLが/hoge/fooのときに/hogeのコンテンツに加えてfooのコンテンツを表示させることができます。SvelteKitのルーティングシステムは柔軟なので、アプリケーションの要件に合わせて最適な方法を選択できます。

KATO-Hiro avatar Feb 27 '25 01:02 KATO-Hiro

SvelteKitでの複数ルート表示方法の比較

各アプローチの長所、短所、適したユースケースを以下に詳しく解説します。

1. ネストされたレイアウトを使用する方法

長所

  • SvelteKitの規約に沿っている: レイアウトシステムはSvelteKitの標準機能である
  • コードの再利用性: 共通レイアウトを一箇所で管理でき、複数ページで再利用できる
  • SEO対応: 各ルートは独自のページとして扱われるため、SEO最適化がしやすい
  • ブラウザ履歴の管理: URLが変わるため、ブラウザの戻るボタンが期待通り動作する

短所

  • ページ遷移が発生する: 完全なページ更新が発生するため、ユーザー体験が損なわれる可能性がある
  • 状態の維持が難しい: ページ間の遷移で状態を保持するには追加の実装が必要

ユースケース

  • ドキュメントサイト(メニューは固定で本文だけ変わる)
  • 管理画面(サイドバーは固定でメインコンテンツが変わる)
  • マルチステップフォーム(進捗状況を表示しながら内容が変わる)

2. ページパラメータを使用する方法

長所

  • SPAのような体験: ページ全体を再読み込みせずにコンテンツを動的に変更できる
  • 状態の保持: ページ遷移なしでコンポーネント状態を保持できる
  • 柔軟性: URLパラメータに基づいて様々なコンテンツの組み合わせが可能

短所

  • 実装の複雑さ: 条件分岐が多くなると管理が難しくなる
  • コンポーネント間の結合度: ページ間の依存関係が強くなりがち
  • 初期ロード時間: すべての可能なコンテンツをロードすると初期表示が遅くなる可能性がある

ユースケース

  • ダッシュボード(タブ切り替えでURLも変わる)
  • フィルタリング機能のあるリスト表示
  • 詳細情報の展開(リストから項目を選んで詳細を同ページに表示)

3. ロードファンクションを使用する方法

長所

  • データ取得の分離: UIとデータ取得ロジックを明確に分離できる
  • SSR対応: サーバーサイドレンダリングと相性が良い
  • 効率的なデータ取得: 必要なデータのみを条件付きで取得できる
  • 型の安全性: TypeScriptと組み合わせると型安全なデータの受け渡しが可能

短所

  • 実装の手間: 各ページでロードファンクションを実装する必要がある
  • 複雑なデータ依存関係: 複数のデータ取得パターンがあると管理が難しくなる
  • パフォーマンスへの影響: 多くのデータを取得するとレンダリングが遅くなる可能性がある

ユースケース

  • APIからデータを取得して表示するページ
  • 認証が必要なコンテンツ
  • 条件に基づいて異なるデータセットを表示するページ

4. 追加オプション: スプリットパネルUIの実装

上記3つの方法を組み合わせた応用例として、スプリットパネルUIが考えられます。

<script>
  import { getContext } from 'svelte';
  import { page } from '$app/stores';
  import { goto } from '$app/navigation';
  
  // 状態管理用のrunes
  const mainPath = $state('/hoge');
  let detailPath = $derived($page.url.pathname.startsWith('/hoge/') ? $page.url.pathname : null);
  let showDetail = $derived(!!detailPath);
  
  // パネルを開く関数
  function openDetailPanel(id) {
    goto(`/hoge/${id}`, { keepfocus: true });
  }
  
  // パネルを閉じる関数
  function closeDetailPanel() {
    goto('/hoge', { keepfocus: true });
  }
</script>

<div class="split-panel-layout">
  <div class="main-panel">
    <!-- メインコンテンツ -->
    <h1>メインコンテンツ</h1>
    <ul>
      <li><button on:click={() => openDetailPanel('foo')}>Fooの詳細を見る</button></li>
      <li><button on:click={() => openDetailPanel('bar')}>Barの詳細を見る</button></li>
    </ul>
  </div>
  
  {#if showDetail}
    <div class="detail-panel">
      <button on:click={closeDetailPanel}>閉じる</button>
      
      <!-- URLに基づいて動的にコンポーネントを読み込む -->
      {#if detailPath === '/hoge/foo'}
        <svelte:component this={import('./foo/+page.svelte')} />
      {:else if detailPath === '/hoge/bar'}
        <svelte:component this={import('./bar/+page.svelte')} />
      {/if}
    </div>
  {/if}
</div>

<style>
  .split-panel-layout {
    display: flex;
    height: 100%;
  }
  
  .main-panel {
    flex: 1;
  }
  
  .detail-panel {
    flex: 1;
    border-left: 1px solid #ccc;
    padding: 1rem;
  }
</style>

主な変更点:

  1. $:リアクティブステートメントを$state$derivedに置き換え

    • mainPath$stateで定義(実際には変更されていないのでconstでも問題ありません)
    • detailPathshowDetail$derivedで定義(計算値のため)
  2. onMountのインポートを削除(この例では使用されていないため)

  3. $pageストアの使用方法は基本的に同じですが、runesモードではストア値へのアクセスの仕方が若干異なります

注意点として、Svelte v5はまだ開発中であり、runesの正確な構文やAPIは変更される可能性があります。最新のドキュメントを参照することをお勧めします。 主な変更点:

  1. $:リアクティブステートメントを$state$derivedに置き換え

    • mainPath$stateで定義(実際には変更されていないのでconstでも問題ありません)
    • detailPathshowDetail$derivedで定義(計算値のため)
  2. onMountのインポートを削除(この例では使用されていないため)

  3. $pageストアの使用方法は基本的に同じですが、runesモードではストア値へのアクセスの仕方が若干異なります

選定の指針

  • SEOが重要: レイアウトベースのアプローチ
  • ユーザー体験を重視: パラメータベースのアプローチ
  • データ駆動型アプリ: ロードファンクションアプローチ
  • 複雑なUI: 複数アプローチの組み合わせ

適切な方法は、アプリケーションの要件、複雑さ、およびターゲットユーザーの期待に基づいて選定するのが望ましいです。

KATO-Hiro avatar Feb 27 '25 01:02 KATO-Hiro