ディレクトリベースのルーティング、+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> // 【B】on 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
ファイルに全て記述
<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
を適切に処理します。
<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
関数を使います。
例を見た方がわかりやすいと思いますので、早速。
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 に渡される。
}
<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
してくれる という便利な特徴があります。
// === さっきの書き方(きちんとawaitを書く) ====
export function load({ fetch }){
async function getdata(){ // async の関数作って
const x =await fetch( '...' ) // fetch は await で実行
return await x.json() // resolve したものしか返さない
}
return getdata() // +page.svelte へ
}
// ==== これでも正常動作する ====
export function load({ fetch }){
return fetch( '...' ).then( x=> x.json() ) // 自動で await してくれる
}
そしてこの勝手にawait
は return 対象の第1階層まで効くようです。
export function load({ fetch }){
return <Promise> // await。
return {
data: <Promise> // await。
}
return {
data: {
players: <Promise> // Promise のまま +page.svelte へ
}
}
}
⑤ +page.js
で取得しつつローディング表示
つまり、データ処理を+page.js
に引っ込めつつ、先にレンダリングという荒業も可能。
export function load({ fetch }) {
return {
level1: {
level2: fetch('https://jwnr-hono.deno.dev/').then( x=> x.json() )
} // 2階層目に Promise を入れる
}
}
<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関数
の活用です。
公式ドキュメントの該当ページを読んでみてください。
本家 / 日本語版