はじめに
SvelteKit を使っているプロジェクトに簡易的な Server State が欲しかったので作ってみた。
環境
- Node.js 18.x
- Svelte 4.x
- SvelteKit 1.x
- Vitest 0.33.x
- msw 1.x
Server State とは
ここで言う Server State とは バックエンドの Web API からのレスポンスを一時的にフロントエンドでキャッシュし管理することを言う。
React だと TanStack Query や SWR が有名で、Svelte にも TanStack Query の Svelte 版 があるのだが、Server State 系のライブラリはけっこう高機能でかつ API が複雑なものが多く、導入のハードルも高く、保守もそこまで容易ではない。
そのため、簡単な要件を満たす簡易的な Server State を Svelte のカスタムストアで作ることにした。
要件
- JavaScript のメモリにデータ (Server State) を格納する
- ページ (ルート) が遷移しても、データは保持され続ける
- ブラウザのリロードなどが発生した場合は、データがクリアされてもいい
- 一定期間が過ぎるとデータは破棄され、再度取得される
- 一定のデータ量を超えた場合、すべてのデータを破棄する
おそらくこれ以上のものが必要な場合は、素直に何らかのライブラリを使ったほうがいいかもしれない。
作ってみる
まずはじめに最小限な機能を持ったものを作る:
エラーハンドリングが雑だが、返されるレスポンスの HTTP ステータスコードが 4xx ならすべて “Not Found”、5xx なら “Internal Server Error” がエラーオブジェクトにセットされる。これでエラーが発生した場合にどこに原因があるかを安易ではあるが確認することができる。
Svelte テンプレートでは以下のようにして使う:
serverState<データの型>(“キー”); でデータの状態を定義し、任意のタイミングで serverState.fetch() を呼び出す。今回はバックエンドを用意していないので JSONPlaceholder を利用している。
Web ブラウザの DevTools などを利用して Fetch API が呼び出されているかを確認する。Google Chrome の場合は DevTools > Network で Fetch/XHR などに絞ってリクエストの詳細を確認することができる。
todos1, todos2, todos3 のリンクをクリックしても、おそらく毎回リクエストが発生している。つまり、今のままではサーバから取得したデータはキャッシュされていないことになる。
テストを書く
とりあえずここまで書いたコードのテストを追加する。Vitest とグローバルな fetch をモックする msw を使って書く。
Vitest と msw のインストール:
npm i vitest msw
いくつかの設定ファイルを更新:
テストの作成:
ストアの初期値とグローバル fetch のレスポンスのテスト (正常系・異常系) くらいでとりあえず OK。
データのキャッシュ機能を追加する
次にサーバからのレスポンスをキャッシュする機能を追加する。いろいろな実装方法があると思うが、今回はデータのサイズやクリアを容易にするために Map オブジェクトを使う。
再度、Web ブラウザの DevTools などを利用して Fetch API のリクエスト回数を確認する。データのキャッシュ機能を追加したことによって、todos1, todos2, todos3 のリンクをクリックしても、初回のみ Fetch API のリクエストが発生し、2 回目以降はリクエストされずキャッシュされたデータが使われているはず。
それらを保証するテストを追加しておく:
キャッシュを破棄する機能を追加する
このままではキャッシュされたデータはブラウザのリロードなどが発生しない限り無期限で使われることになる。例えば、バックエンド側のデータになんらかの変更があった場合、フロントエンド側との整合性が即座に失われる。
それらを改善する方法はいくつかあると思うが、今回はあくまで「簡易的な Server State を作ること」が目的であるため、キャッシュされたデータの破棄も簡易的に実装する。
- キャッシュされたデータが 100 個以上格納されている場合、すべてを破棄する
- キャッシュされたデータに有効期限を設ける
カスタムストアを以下のように更新する:
カスタムストアに新たな引数が追加されたため、Svelte テンプレートも更新する:
テストも更新する:
これで一応の完成である。
ブラウザで検証したい場合は、revalidate の値を数秒にしたり、キャッシュのアイテム数を数個にしてみたりして調整する。
まとめ
今回は SvelteKit で簡易的な Server State を作ってみた。Svelte には TanStack Query の Svelte 版 があるので、それを使えばより高機能ものを実装できる。が、それらのライブラリをしっかりと理解したうえで使用し、長期的に保守できるかと自問すると「けっこう大変」という答えになる。
React 版の TanStack Query も React の Server Component や Next.js の App Router が出てきたあたりから、どのように使い分ければいいんだろう?と迷うことがよくある。この問題は現在進行中であり、Next.js の Server Actions が stable にならない限り解決できない問題ではあるが、いずれにせよ、便利なライブラリを長期に渡って使い続けていくのがより難しくなってきているなぁと最近よく感じる。
そのような理由から、今回は何かしらのライブラリを利用するのを避けて、自作するという選択をした。もちろん、要件が多く複雑である場合は、結果的に自作するほうがいろいろ大変になることもあるので注意する。
関連リンク