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


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



#### 本記事で扱うファイル

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


---

シリーズまとめ（随時追加・更新）

| | |
|:-|:-|
| [【SvelteKit 入門】はじめに](https://qiita.com/jwnr/items/430cd160e90fdfa42336) |  |
| [【SvelteKit 入門】作業の前に](https://qiita.com/jwnr/items/9bb7583b5e3d9f75289c) |  |
| [【SvelteKit 入門】アダプター設定・ホスティング・コンテナ運用](https://qiita.com/jwnr/items/740335cfb7d8e000c749) |  |
| [【SvelteKit 入門】ルーティング](https://qiita.com/jwnr/items/8932978ca2f50f102e3d) |  |
| 【SvelteKit 入門】データハンドリング(+page.js) | now reading |
| [SvelteKit + microCMS でブログ構築](https://qiita.com/jwnr/items/c7a153f2d1555cc789fe) |  |

------



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

まず以下の図を見てください
|||
|:-:|:--|
|ユーザーからのアクセス| &emsp;&emsp;&emsp; |
|⇓| ← ① 画面描画の **前** &emsp; |
|ユーザーに画面を表示|← ② 画面描画の **実行中** &emsp; |
||← ③ 画面描画の **後** &emsp; |

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

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

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

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

||||
|:-:|:--|:-:|
|ユーザーからのアクセス| &emsp;&emsp;&emsp; ||
|⇓| ← ① 画面描画の **前** &emsp; ||
|ユーザーに画面を表示|← ② 画面描画の **実行中** &emsp; |&emsp;【A】&emsp;|
||← ③ 画面描画の **後** &emsp; |&emsp;【B】&emsp;|

`.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 がそれらを連動させ、結果をブラウザに出力します。

![](https://storage.googleapis.com/zenn-user-upload/3af4396185fd-20220926.png)
*データの流れ*



### ■ `+ .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`で良くない？**

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

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


### ① `.svelte`ファイルに全て記述
```js:+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`を適切に処理します。

```js:/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`関数を使います。
例を見た方がわかりやすいと思いますので、早速。

```js:+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 に渡される。

}
```

```js:+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`してくれる** という便利な特徴があります。

```js:+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 へ

}
```

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

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

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

    return <Promise>             // await。

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

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


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

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

```js:+page.js
export function load({ fetch }) {
    return {
        level1: {
            level2: fetch('https://jwnr-hono.deno.dev/').then( x=> x.json() )
        }   // 2階層目に Promise を入れる
    }
}
```

```js:+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`を使う事が多い気がします。

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

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


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