生成的データ インテリジェンス

SvelteKit でのデータのキャッシュ

日付:

My 以前の投稿 は、SvelteKit の広範な概要であり、Web 開発のための優れたツールであることがわかりました。 この投稿では、そこで行ったことを分岐させて、すべての開発者のお気に入りのトピックに飛び込みます。 キャッシング. ですから、まだ読んでいない場合は、必ず私の最後の投稿を読んでください。 この記事のコード GitHubで入手できます、 と同様 ライブデモ.

この投稿はすべてデータ処理に関するものです。 ページのクエリ文字列を (組み込みの SvelteKit 機能を使用して) 変更し、ページのローダーを再トリガーする基本的な検索機能をいくつか追加します。 ただし、(架空の) データベースを再クエリするだけでなく、キャッシュを追加して、以前の検索を再検索する (または [戻る] ボタンを使用する) と、キャッシュから以前に取得したデータがすばやく表示されるようにします。 キャッシュされたデータが有効である期間を制御する方法と、さらに重要なこととして、キャッシュされたすべての値を手動で無効にする方法について説明します。 おまけとして、現在の画面のクライアント側のデータをミューテーション後に手動で更新しながら、キャッシュを消去する方法を見ていきます。

これは、より難しいトピックを扱っているため、私が通常書いているほとんどの記事よりも長く、より難しい記事になります。 この投稿では、基本的に、次のような一般的なデータ ユーティリティの一般的な機能を実装する方法を示します。 反応クエリ; ただし、外部ライブラリを取り込む代わりに、Web プラットフォームと SvelteKit 機能のみを使用します。

残念ながら、Web プラットフォームの機能は少しレベルが低いため、これまでよりも少し多くの作業を行う予定です。 利点は、外部ライブラリが必要ないことです。これにより、バンドルのサイズを適切かつ小さく保つことができます。 正当な理由がない限り、これから紹介するアプローチを使用しないでください。 キャッシングは間違いを犯しやすく、後でわかるように、アプリケーション コードは多少複雑になります。 データ ストアが高速であり、UI が適切で、SvelteKit が特定のページに必要なデータを常に要求できることを願っています。 もしそうなら、放っておいてください。 シンプルさを楽しんでください。 しかし、この投稿では、それができなくなった場合のいくつかの秘訣を紹介します。

反応クエリといえば、 リリースされたばかり スヴェルテに! したがって、手動キャッシング手法に頼っていることに気付いた場合は、 たくさん、そのプロジェクトをチェックアウトして、役立つかどうかを確認してください。

セットアップ

始める前に、いくつかの小さな変更を加えましょう 前に持っていたコード. これにより、他の SvelteKit 機能を確認する口実が得られ、さらに重要なことに、成功への準備が整います。

まず、データの読み込みをローダーから移動しましょう +page.server.js 〜に APIルート. 作成します +server.js 内のファイル routes/api/todos、次に追加します GET 関数。 これは、(デフォルトの GET 動詞を使用して) フェッチできるようになったことを意味します。 /api/todos 道。 前と同じデータ読み込みコードを追加します。

import { json } from "@sveltejs/kit";
import { getTodos } from "$lib/data/todoData"; export async function GET({ url, setHeaders, request }) { const search = url.searchParams.get("search") || ""; const todos = await getTodos(search); return json(todos);
}

次に、持っていたページ ローダーを使用して、単純にファイルの名前を +page.server.js 〜へ +page.js (または .ts TypeScript を使用するようにプロジェクトをスキャフォールディングした場合)。 これにより、ローダーがサーバー ローダーではなく「ユニバーサル」ローダーに変更されます。 SvelteKit ドキュメント 違いを説明する、ただし、ユニバーサル ローダーはサーバーとクライアントの両方で実行されます。 私たちにとっての利点の XNUMX つは、 fetch 新しいエンドポイントへの呼び出しは、ブラウザーのネイティブを使用して、ブラウザーから直接実行されます (初期ロード後)。 fetch 関数。 後で標準の HTTP キャッシングを追加しますが、今のところは、エンドポイントを呼び出すだけです。

export async function load({ fetch, url, setHeaders }) { const search = url.searchParams.get("search") || ""; const resp = await fetch(`/api/todos?search=${encodeURIComponent(search)}`); const todos = await resp.json(); return { todos, };
}

それでは、簡単なフォームを追加しましょう /list ページ:

<div class="search-form"> <form action="/ja/list"> <label>Search</label> <input autofocus name="search" /> </form>
</div>

はい、フォームは通常のページ ローダーを直接ターゲットにすることができます。 検索ボックスに検索語を追加して、 入力します、「検索」用語が URL のクエリ文字列に追加され、ローダーが再実行され、To Do 項目が検索されます。

検索フォーム

また、遅延を増やしましょう todoData.js 内のファイル /lib/data. これにより、この記事の作業中に、データがキャッシュされている場合とキャッシュされていない場合を簡単に確認できます。

export const wait = async amount => new Promise(res => setTimeout(res, amount ?? 500));

この投稿の完全なコードは すべて GitHub で、参照する必要がある場合。

基本キャッシング

キャッシングを追加することから始めましょう /api/todos 終点。 私たちは私たちに戻ります +server.js ファイルを作成し、最初のキャッシュ制御ヘッダーを追加します。

setHeaders({ "cache-control": "max-age=60",
});

…関数全体は次のようになります。

export async function GET({ url, setHeaders, request }) { const search = url.searchParams.get("search") || ""; setHeaders({ "cache-control": "max-age=60", }); const todos = await getTodos(search); return json(todos);
}

手動による無効化については後ほど説明しますが、この関数が示すのは、これらの API 呼び出しを 60 秒間キャッシュすることだけです。 これを好きなように設定してください、ユースケースに応じて、 stale-while-revalidate も検討する価値があるかもしれません。

このように、クエリはキャッシュされます。

DevTools にキャッシュします。

Note 確認してください チェックを外します 開発ツールでキャッシュを無効にするチェックボックス。

アプリの最初のナビゲーションがリスト ページである場合、それらの検索結果は SvelteKit に内部的にキャッシュされるため、その検索に戻ったときに DevTools に何も表示されないことを忘れないでください。

何がキャッシュされ、どこにキャッシュされるか

サーバーでレンダリングされた最初のアプリの読み込み ( /list ページ) がサーバーにフェッチされます。 SvelteKit はこのデータをシリアル化し、クライアントに送信します。 さらに、それは観察します Cache-Control ヘッダ 応答で、このキャッシュされたデータをキャッシュ ウィンドウ内でそのエンドポイント呼び出しに使用することがわかります (例では 60 秒に設定されています)。

その初期ロードの後、ページで検索を開始すると、ブラウザから /api/todos リスト。 既に (過去 60 秒以内に) 検索したものを検索すると、応答はキャッシュされているため、すぐに読み込まれます。

このアプローチの特に優れた点は、これはブラウザーのネイティブ キャッシュを介したキャッシュであるため、これらの呼び出しは (これから説明するキャッシュ無効化の管理方法に応じて) ページをリロードしてもキャッシュし続ける可能性があることです (最初のサーバー側の負荷。最後の 60 秒以内にエンドポイントが実行された場合でも、エンドポイントを常にフレッシュに呼び出します)。

明らかに、データはいつでも変更される可能性があるため、このキャッシュを手動で消去する方法が必要です。これについては次に説明します。

キャッシュの無効化

現在、データは 60 秒間キャッシュされます。 何があっても、XNUMX 分後にデータストアから新しいデータが取得されます。 期間を短くしたり長くしたりしたいかもしれませんが、一部のデータを変更し、すぐにキャッシュをクリアして次のクエリが最新になるようにしたい場合はどうなるでしょうか? これを解決するには、新しい URL に送信する URL にクエリ無効化の値を追加します。 /todos エンドポイントを使用して定義します

このキャッシュ無効化の値を Cookie に保存しましょう。 その値はサーバーで設定できますが、クライアントで読み取ることができます。 サンプルコードを見てみましょう。

作成できます +layout.server.js 私たちのルートにあるファイル routes フォルダ。 これはアプリケーションの起動時に実行され、Cookie の初期値を設定するのに最適な場所です。

export function load({ cookies, isDataRequest }) { const initialRequest = !isDataRequest; const cacheValue = initialRequest ? +new Date() : cookies.get("todos-cache"); if (initialRequest) { cookies.set("todos-cache", cacheValue, { path: "/", httpOnly: false }); } return { todosCacheBust: cacheValue, };
}

あなたは気づいたかもしれません isDataRequest 価値。 レイアウトは、クライアント コードが呼び出されるたびに再実行されることに注意してください。 invalidate()、またはサーバー アクションを実行するたびに (デフォルトの動作をオフにしないと仮定して)。 isDataRequest これらの再実行を示します。 false; それ以外の場合は、既にあるものを送信します。

  httpOnly: false フラグも重要です。 これにより、クライアント コードでこれらの Cookie 値を読み取ることができます。 document.cookie. これは通常、セキュリティ上の問題になりますが、私たちの場合、これらは無意味な数値であり、キャッシュまたはキャッシュ バストを可能にします。

キャッシュ値の読み取り

私たちのユニバーサルローダーは、 /todos 終点。 これはサーバーまたはクライアントで実行され、どこにいても設定したばかりのキャッシュ値を読み取る必要があります。 サーバー上にいる場合は比較的簡単です。呼び出すことができます await parent() 親レイアウトからデータを取得します。 ただし、クライアントでは、解析するためにいくつかのグロス コードを使用する必要があります。 document.cookie:

export function getCookieLookup() { if (typeof document !== "object") { return {}; } return document.cookie.split("; ").reduce((lookup, v) => { const parts = v.split("="); lookup[parts[0]] = parts[1]; return lookup; }, {});
} const getCurrentCookieValue = name => { const cookies = getCookieLookup(); return cookies[name] ?? "";
};

幸いなことに、必要なのは一度だけです。

キャッシュ値の送信

しかし今、私たちはする必要があります 送信 この価値を私たちに /todos エンドポイントを使用して定義します

import { getCurrentCookieValue } from "$lib/util/cookieUtils"; export async function load({ fetch, parent, url, setHeaders }) { const parentData = await parent(); const cacheBust = getCurrentCookieValue("todos-cache") || parentData.todosCacheBust; const search = url.searchParams.get("search") || ""; const resp = await fetch(`/api/todos?search=${encodeURIComponent(search)}&cache=${cacheBust}`); const todos = await resp.json(); return { todos, };
}

getCurrentCookieValue('todos-cache') は、(ドキュメントのタイプをチェックすることによって) クライアント上にいるかどうかを確認するためのチェックを行い、クライアント上にある場合は何も返しません。この時点で、サーバー上にいることがわかります。 次に、レイアウトの値を使用します。

キャッシュの無効化

だけど 必要なときに実際にそのキャッシュ無効化値を更新しますか? これは Cookie に保存されるため、任意のサーバー アクションから次のように呼び出すことができます。

cookies.set("todos-cache", cacheValue, { path: "/", httpOnly: false });

実装

ここからは下り坂です。 私たちは大変な仕事をしました。 必要なさまざまな Web プラットフォーム プリミティブと、それらの場所について説明しました。 それでは、楽しみながらアプリケーション コードを作成して、すべてを結び付けましょう。

理由は後で明らかになりますが、編集機能を追加することから始めましょう。 /list ページ。 Todo ごとに、この XNUMX 番目のテーブル行を追加します。

import { enhance } from "$app/forms";
<tr> <td colspan="4"> <form use:enhance method="post" action="?/editTodo"> <input name="id" value="{t.id}" type="hidden" /> <input name="title" value="{t.title}" /> <button>Save</button> </form> </td>
</tr>

そしてもちろん、フォーム アクションを追加する必要があります。 /list ページ。 アクションは入ることができます .server ページを追加します +page.server.js 私たちの中で /list フォルダ。 (はい、 +page.server.js ファイルは隣に共存できます +page.js ファイル。)

import { getTodo, updateTodo, wait } from "$lib/data/todoData"; export const actions = { async editTodo({ request, cookies }) { const formData = await request.formData(); const id = formData.get("id"); const newTitle = formData.get("title"); await wait(250); updateTodo(id, newTitle); cookies.set("todos-cache", +new Date(), { path: "/", httpOnly: false }); },
};

フォーム データを取得し、遅延を強制し、Todo を更新し、最も重要なこととして、キャッシュ バスト Cookie をクリアしています。

これを試してみましょう。 ページを再読み込みしてから、To Do 項目の XNUMX つを編集します。 しばらくすると、テーブルの値が更新されます。 DevToold の Network タブを見ると、 /todos 新しいデータを返すエンドポイント。 シンプルで、デフォルトで動作します。

データの保存

即時更新

To Do アイテムを更新した後にフェッチが行われるのを避け、変更したアイテムを画面上で直接更新したい場合はどうすればよいでしょうか?

これは単なるパフォーマンスの問題ではありません。 「投稿」を検索して、リスト内のいずれかの To Do 項目から「投稿」という単語を削除すると、そのページの検索結果に表示されなくなるため、編集後にリストから削除されます。 終了する To-Do に上品なアニメーションを使用して UX を改善することもできますが、そうしたかったとしましょう。 そのページのロード機能を再実行しますが、それでもキャッシュをクリアし、変更された To-Do を更新して、ユーザーが編集内容を確認できるようにします。 SvelteKit はそれを可能にします — 見てみましょう!

まず、ローダーに少し変更を加えましょう。 To Do アイテムを返す代わりに、 書き込み可能なストア 私たちのやることを含んでいます。

return { todos: writable(todos),
};

以前は、To-Do にアクセスしていました。 data 私たちが所有しておらず、更新できない小道具。 しかし、Svelte では、独自のストアにデータを返すことができます (ユニバーサル ローダーを使用していると仮定します)。 もう XNUMX つ微調整する必要があります。 /list ページで見やすくするために変数を解析したりすることができます。

これの代わりに:

{#each todos as t}

…以来、これを行う必要があります todos それ自体がストアになりました。:

{#each $todos as t}

これで、以前と同じようにデータがロードされます。 しかしそれ以来 todos は書き込み可能なストアなので、更新できます。

まず、私たちに関数を提供しましょう use:enhance 属性:

<form use:enhance={executeSave} on:submit={runInvalidate} method="post" action="?/editTodo"
>

これは送信前に実行されます。 次にそれを書きましょう。

function executeSave({ data }) { const id = data.get("id"); const title = data.get("title"); return async () => { todos.update(list => list.map(todo => { if (todo.id == id) { return Object.assign({}, todo, { title }); } else { return todo; } }) ); };
}

この関数は、 data フォームデータを持つオブジェクト。 私達 return 実行される非同期関数 After 編集が完了しました。 ドキュメント これをすべて説明しますが、これを行うことで、ローダーを再実行する SvelteKit のデフォルトのフォーム処理を停止します。 これはまさに私たちが望んでいるものです! (ドキュメントで説明されているように、デフォルトの動作に簡単に戻すことができます。)

私たちは今、 update 私たちの上 todos ストアなので配列。 そして、それはそれです。 To Do アイテムを編集した後、変更がすぐに表示され、キャッシュがクリアされます (以前と同様に、新しい Cookie 値を editTodo フォームアクション)。 そのため、検索してからこのページに戻ると、ローダーから新しいデータが取得され、更新された更新済みの To Do アイテムが正しく除外されます。

即時更新のコード GitHub で入手できます.

深く掘る

ルート レイアウトだけでなく、任意のサーバー ロード関数 (またはサーバー アクション) で Cookie を設定できます。 したがって、一部のデータが単一のレイアウトまたは単一のページの下でのみ使用される場合、その Cookie 値をそこに設定できます。 さらに、もしあなたが 先ほど示した画面上のデータを手動で更新するトリックを実行し、代わりにローダをミューテーション後に再実行したい場合は、そのロード関数で常に新しい Cookie 値をチェックせずに設定できます。 isDataRequest. これは最初に設定され、その後、ページ レイアウトが自動的に無効にしてローダーを再呼び出しするサーバー アクションを実行するたびに、ユニバーサル ローダーが呼び出される前にキャッシュ バスト文字列を再設定します。

リロード関数を書く

最後の機能であるリロード ボタンを作成してまとめましょう。 キャッシュをクリアして現在のクエリをリロードするボタンをユーザーに提供しましょう。

シンプルなフォーム アクションを追加します。

async reloadTodos({ cookies }) { cookies.set('todos-cache', +new Date(), { path: '/', httpOnly: false });
},

実際のプロジェクトでは、同じコードをコピーして複数の場所に同じ方法で同じ Cookie を設定することはおそらくないでしょうが、この投稿では、単純さと読みやすさのために最適化します。

次に、投稿するフォームを作成しましょう。

<form method="POST" action="?/reloadTodos" use:enhance> <button>Reload todos</button>
</form>

それはうまくいきます!

リロード後のUI。

これで完了と言って次に進むこともできますが、このソリューションを少し改善しましょう。 具体的には、ページにフィードバックを提供して、リロードが発生していることをユーザーに伝えましょう。 また、デフォルトでは、SvelteKit アクションは無効になります すべてのもの. 現在のページの階層にあるすべてのレイアウト、ページなどが再読み込みされます。 ルート レイアウトに一度読み込まれ、無効化または再読み込みする必要のないデータが存在する場合があります。

では、少し焦点を絞って、この関数を呼び出したときにだけ to-do をリロードしてみましょう。

まず、強化する関数を渡しましょう。

<form method="POST" action="?/reloadTodos" use:enhance={reloadTodos}>
import { enhance } from "$app/forms";
import { invalidate } from "$app/navigation"; let reloading = false;
const reloadTodos = () => { reloading = true; return async () => { invalidate("reload:todos").then(() => { reloading = false; }); };
};

新たに設定しております reloading 変数へ true start このアクションの。 そして、すべてを無効にするデフォルトの動作をオーバーライドするために、 async 関数。 この関数は、サーバー アクションが終了したときに実行されます (新しい Cookie を設定するだけです)。

これなしで async 関数が返された場合、SvelteKit はすべてを無効にします。 この関数を提供しているので、何も無効にしないため、何をリロードするかを指定するのは私たち次第です。 私たちはこれを invalidate 関数。 の値で呼び出します。 reload:todos. この関数は、無効化が完了すると解決する promise を返します。 reloading バックに false.

最後に、ローダーをこの新しいものと同期する必要があります reload:todos 無効値。 ローダーでそれを行います depends 関数:

export async function load({ fetch, url, setHeaders, depends }) { depends('reload:todos'); // rest is the same

そして、それはそれです。 depends & invalidate 非常に便利な機能です。 かっこいいってことは invalidate 私たちが行ったように、私たちが提供する任意の値を取るだけではありません。 また、SvelteKit が追跡する URL を提供し、その URL に依存するローダーを無効にすることもできます。 そのために、呼び出しをスキップできるかどうか疑問に思っている場合は、 depends そして私たちを無効にします /api/todos エンドポイント全体で、できますが、提供する必要があります 正確な を含む URL search term (およびキャッシュ値)。 そのため、次のように、現在の検索の URL をまとめるか、パス名で一致させることができます。

invalidate(url => url.pathname == "/api/todos");

個人的に、私は使用するソリューションを見つけます depends より明示的かつシンプルに。 しかし、見てください ドキュメント もちろん、詳細については、自分で決めてください。

リロード ボタンの動作を確認したい場合は、そのコードを以下に示します。 レポのこのブランチ.

別れの考え

これは長い投稿でしたが、圧倒されないことを願っています。 SvelteKit を使用するときにデータをキャッシュするさまざまな方法を詳しく説明します。 これの多くは、Web プラットフォーム プリミティブを使用して正しいキャッシュと Cookie 値を追加するだけの問題でした。これらの知識は、SvelteKit だけでなく、Web 開発全般に役立ちます。

また、これはあなたが絶対に何か ずっといらない. おそらく、これらの高度な機能は、次の場合にのみ使用する必要があります。 実際にそれらが必要です. データストアがデータを迅速かつ効率的に提供しており、スケーリングの問題に対処していない場合、ここで説明したことを行うためにアプリケーション コードを不必要に複雑にして肥大化させることは意味がありません。

いつものように、明確でクリーンでシンプルなコードを書き、必要に応じて最適化してください。 この投稿の目的は、本当に必要なときに最適化ツールを提供することでした。 お楽しみいただけたでしょうか。

スポット画像

最新のインテリジェンス

スポット画像

私たちとチャット

やあ! どんな御用でしょうか?