1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【SvelteKit 入門】データハンドリング(+page.js)

Last updated at Posted at 2022-10-22

ディレクトリベースのルーティング、+page.svelteによるページ表示、
次はデータ処理に特化したファイルを使った データハンドリングです。

本記事で扱うファイル

+page.svelte +page.js +page.server.js


シリーズまとめ(随時追加・更新)

【SvelteKit 入門】はじめに
【SvelteKit 入門】作業の前に
【SvelteKit 入門】アダプター設定・ホスティング・コンテナ運用
【SvelteKit 入門】ルーティング
【SvelteKit 入門】データハンドリング(+page.js) now reading
SvelteKit + microCMS でブログ構築

+page.svelte内のコード実行タイミング

まず以下の図を見てください

ユーザーからのアクセス    
← ① 画面描画の
ユーザーに画面を表示 ← ② 画面描画の 実行中
← ③ 画面描画の

次に、以下のコード例を見てください。

<script>
    // 【A】HTML要素の元となるデータ
    let items =[ '緒方', '金本', '前田' ]
  
    // 【B】ユーザーアクションで動作する処理
    function alrt(){
        alert('クリックされました')
    }
</script>

<ul>
    {#each items as item}                  // 【A】データを元に描画
        <li on:click={alrt}>{item}</li>    // Bon click イベント紐付け
    {/each}
</ul>

このコード内にある処理について、最初の表に追記すると以下のようになります。

ユーザーからのアクセス    
← ① 画面描画の
ユーザーに画面を表示 ← ② 画面描画の 実行中  【A】 
← ③ 画面描画の  【B】 

.svelteファイルに記述し構築していた画面というのは アクセスが回って来てからの話(②,③) であり、その前(①の部分)というのは.svelteファイルにとっては関与できない領域です。

しかし SvelteKit はユーザーからのアクセスを捌くルーティングを行っているため、 ①のタイミングに処理を入れる事が可能です

+page.js, +page.server.js の利用

SvelteKitのルーティングは ディレクトリベース です。

プロジェクト/
    ...
    ├ routes/
    │    ├ xxx/                    <- ディレクトリ名がURLに対応する
    │    │    ├ +page.svelte       <- *必須*
    │    │    ├ +page.js           <- (任意)
    │    │    └ +page.server.js    <- (任意)
    │    ├ ...

表示画面を構築するのはディレクトリ直下の+page.svelte。これは必須ファイル。
必要に応じて データ取得, 処理に特化した+page.js, +page.server.jsを配置 することで、SvelteKit がそれらを連動させ、結果をブラウザに出力します。


データの流れ

+ .jsファイルの特徴まとめ

  • +page.svelteによる画面描画の前、つまり先ほど言及した ①のタイミングで実行される
  • Requestへのアクセス(cookie取得)・パスパラメータの取得・リダイレクト等も可能
  • サーバー実行(固定)もできるので、秘匿データを扱える

認証情報を読み取りログインページにリダイレクトしたり、キーをヘッダーに入れて外部APIにアクセスしたり、.svelteファイルでは出来ない処理を担当します。

+page.js/+page.server.jsの使い分け

違いは 実行場所 です

+page.js サーバー or ブラウザ
+page.server.js サーバーのみ

SvelteKit のレンダリングは SSR と CSR を組み合わせるので、+page.jsの実行場所は状況により サーバーだったりブラウザだったりします。一方で+page.server.jsは常にサーバーで実行されます。

【本題】+page.jsによるデータ取得

+page.jsを使えばデータを+page.svelteに渡せる。レンダリング前に実行できる。

でも+page.svelteで良くない?

そうなんですよね…
そこで、いくつかの実装方法を順番に検証しながら最適解を考えます。

検証用に簡単なAPIを用意しました。
読み込みの挙動を確認しやすいように、1秒弱待機してからレスポンスを返します。
https://jwnr-hono.deno.dev/

.svelteファイルに全て記述

+page.svelte
<script>
    let players =[]  // データ格納用の変数(空配列で初期化)
    fetch( 'https://jwnr-hono.deno.dev/' )
    .then( x=> x.json() ).then( x=> {
        players =x.players
    })
</script>


{#each players as player}
    <p> {player.name} </p>
{/each}

.svelteファイルの<script>部分にそのまま非同期処理を書いた場合、HTML部分のレンダリングは resolve を待ちません。その際 #eachの対象変数にIterableなデータが入ってないとエラーになるので、初期値は空配列にしてます。

空配列[]のまま初期描画 → fetch完了でデータが入る → 自動で再描画される

という流れ。

{#await}活用

データの取得→描画は出来ましたが、これではfetch完了まで空白になりますね。
Svelte の{#await}を使い、fetchが返すPromiseを適切に処理します。

/src/routes/blog/+page.svelte
<script>
    // getdata には Promise が入り、resolve 後にデータが返る
    let getdata =fetch( 'https://jwnr-hono.deno.dev/' ).then( x=> x.json() )
</script>


{#await getdata}
    <p>取得中</p>
{:then resolveData}
    {#each resolveData.players as player}
        <p> {player.name} </p>
    {/each}
{/await}

(これで全然問題ないじゃん・・・)

+page.jsでデータ処理を引き受ける

+page.svelteと連動してデータを受け渡す場合、load関数を使います。
例を見た方がわかりやすいと思いますので、早速。

+page.js
export function load({ fetch }) {  // <- ここの引数 fetch は後述

    // データ取得の処理
    async function getdata() {
        const x = await fetch('https://jwnr-hono.deno.dev/')
        return    await x.json()
    }
    return getdata()
    // load 関数内で return した値が +page.svelte に渡される。

}
+page.svelte
<script>
  export let data  // +page.js からデータ受け取り
  // ※ fetch の resolve 待ちは +page.js で済んでる
</script>


{#each data as player}
    <p> {player.name} </p>
{/each}

①との大きな違いは、fetch完了までがページ読み込みになる という点ですね。
ページ読み込み・描画 → fetch完了 → 再描画 という流れではなく、
ページ読み込み自体が待機 → fetch完了 → レンダリング開始 となります。

※ load関数 独自のfetch

load({ fetch }){...load関数から受け取る ことで、この関数内では標準の fetch を上書きしています。この load関数独自のfetch は、標準のものにいくつか機能を上乗せしたようなものです。
特定の使用方法だと この上乗せ機能が便利 なので、load関数内で fetch を使う場合は読み込んでおけば安心。今回のケースは標準fetchでいけますけどね。

+page.jsの独特な挙動

return で返すのは基本的にオブジェクトですが、Promise を入れると自動的にawaitしてくれる という便利な特徴があります。

+page.js
// === さっきの書き方(きちんとawaitを書く) ====
export function load({ fetch }){

    async function getdata(){           // async の関数作って
        const x =await fetch( '...' )   // fetch は await で実行
        return   await x.json()         // resolve したものしか返さない
    }
    return getdata()  // +page.svelte へ

}
+page.js
// ==== これでも正常動作する ====
export function load({ fetch }){
    return fetch( '...' ).then( x=> x.json() )  // 自動で await してくれる
}

そしてこの勝手にawaitは return 対象の第1階層まで効くようです。

+page.js
export function load({ fetch }){

    return <Promise>             // await。

    return {
        data: <Promise>          // await。
    }

    return {
        data: {
            players: <Promise>   // Promise のまま +page.svelte へ
        }
    }
}

+page.jsで取得しつつローディング表示

つまり、データ処理を+page.jsに引っ込めつつ、先にレンダリングという荒業も可能。

+page.js
export function load({ fetch }) {
    return {
        level1: {
            level2: fetch('https://jwnr-hono.deno.dev/').then( x=> x.json() )
        }   // 2階層目に Promise を入れる
    }
}
+page.svelte
<script>
    export let data  // Promise はまだ pending 状態
</script>


{#await data.level1.level2}
    <p>取得中</p>
{:then resolveData}
    {#each resolveData.players as player}
        <p> {player.name} </p>
    {/each}
{/await}

(最初から.svelteに書けばよくね・・・?)

結局どの書き方が良いのか

あくまで私の場合ですが・・・
.svelteファイルはアーキテクチャでいう ビュー に専念させる という感覚で分けているので、+page.js,+page.server.jsを使う事が多い気がします。

ただし、アプリケーション開発でコンポーネントに機能を閉じ込めたい場合は、1つの.svelteファイル内に詰め込んだりします。

結局は状況に応じて使い分けることになるので、特に気にせず好きにすれば良いかと思います。

+page.jsを使う最大の目的はload関数の活用です。
公式ドキュメントの該当ページを読んでみてください。
本家 / 日本語版

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?