Mise en cache des clients dans SvelteKit

Découvrez quelques approches de mise en cache dans le client avec SvelteKit et voyez un exemple de mise en œuvre.
Récupération intégrée
Il existe toutes sortes de façons de mettre en cache vos données dans un framework. La manière la plus courante dans SvelteKit est d’utiliser Contrôle du cache en-têtes avec fetch
. Vous pouvez également définir le type de demande de cache en utilisant fetch sur le frontend.
Récupération de SvelteKit est spécial et permet d’appeler ces requêtes directement sur le serveur sans utiliser d’URL directe. Il peut appeler une fonction directement depuis le serveur, au lieu de récupérer directement les données.
Mise en cache des clients
Mais que se passe-t-il si vous souhaitez simplement ne pas lire deux fois les mêmes données ? Une façon de procéder serait d’installer un package complet comme Requête TanStack pour Svelte, mais vous avez alors beaucoup de frais généraux. Imaginez que vous soyez dans une situation où le Wi-Fi est lent et que vous souhaitiez simplement afficher une page vers laquelle vous avez déjà accédé. Imaginez que vous avez déjà téléchargé les informations de publication et que vous souhaitez simplement cliquer sur la page de détail de la publication. Pourquoi auriez-vous besoin de réinterroger la base de données pour les données que vous avez déjà consultées dans votre application ouverte ?
Cette méthode peut également être utile avec des bases de données comme Firebase où vous êtes facturé pour chaque lecture ! J’ai donc décidé de créer un mécanisme de mise en cache rapide pour SvelteKit afin de gérer ces cas d’utilisation pour un chargement plus rapide.
cacheRécupérer
Je voulais quelque chose d’extrêmement simple à utiliser, mais qui pourrait être implémenté dans le page.ts
dans le load
fonction. Il doit récupérer les données en arrière-plan et les mettre en cache par URL. Puisque la récupération nécessite tout cela json
manipulation, on pourrait mettre tout ça là-dedans aussi. Encore une fois, en utilisant un package externe comme Axios serait exagéré.
1. Créez un point de terminaison
Nous devons d’abord simuler une base de données en créant un point de terminaison.
/routes/data/+server.ts
import { error, json, type RequestHandler } from "@sveltejs/kit";
const posts = [ ... ];
export const GET = (async ({ url }) => {
const id = Number(url.searchParams.get('id'));
// simulate loading data slowly
await new Promise((res) => setTimeout(res, 2000));
if (id) {
const post = posts.find(post => post.id === id);
if (!post) {
throw error(404, 'Post not found!');
}
return json(post);
}
return json(posts);
}) satisfies RequestHandler;
Tout cela ne fait qu’obtenir tous les messages s’il y a une demande directe à /data
et obtenez un message spécifique s’il y a une demande de /data?id=x
. La promesse superflue ici simulera une base de données lente afin que vous puissiez voir le cache en action.
Je viens de demander à l’IA de générer des données factices, mais vous pouvez utiliser n’importe quoi :
const posts = [
{
"id": 1,
"title": "The Joy of Gardening",
"content": "Discover the endless benefits of spending time in your garden. From growing your own food to the therapeutic aspects of gardening, this post explores why gardening is a hobby worth considering.",
"author": "Alex Smith",
"created_at": "2023-11-19T08:00:00Z"
},
{
"id": 2,
"title": "Tech Trends in 2023",
"content": "In this post, we dive into the latest technology trends of 2023. We cover everything from AI advancements to sustainable tech solutions that are shaping our world.",
"author": "Jordan Lee",
"created_at": "2023-11-18T15:30:00Z"
},
{
"id": 3,
"title": "Exploring World Cuisines",
"content": "Join us on a culinary journey as we explore different world cuisines. From the spicy dishes of Mexico to the intricate flavors of Japanese cuisine, get ready to expand your taste palette.",
"author": "Maria Gonzalez",
"created_at": "2023-11-17T19:45:00Z"
}
];
2. Créez les modèles
J’ai créé un modèle de publication de mise en page de base pour afficher les publications. Assurez-vous d’installer Vent arrière dans votre application SvelteKit vierge en premier.
/routes/+layout.svelte
<script>
import { navigating } from '$app/stores';
import '../app.css';
</script>
<nav class="m-5">
<h1 class="text-xl underline hover:no-underline"><a href={`/`}>Home</a></h1>
</nav>
{#if $navigating}
<div
class="m-5 inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"
role="status"
>
<span
class="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]"
>
Loading...
</span>
</div>
{:else}
<main class="m-5">
<slot />
</main>
{/if}
Cela affichera une icône de chargement lorsque la page est en cours de navigation ou de récupération. Le $navigating
est une astuce du mécanisme Svelte pour ce faire.
/routes/+page.svelte
<script lang="ts">
import type { PageData } from './$types';
import Post from '@components/post.svelte';
export let data: PageData;
</script>
<h1 class="text-3xl font-bold underline">Posts</h1>
{#each data.posts as post}
<Post {post} />
{/each}
Cela montre tous les messages une fois récupérés.
/itinéraires/poste/urn:uuid:304ed28e-4911-41ef-8a8e-e0879d531a38/+page.svelte
<script lang="ts">
import type { PageData } from './$types';
import Post from '@components/post.svelte';
export let data: PageData;
</script>
<Post post={data.post} />
Cela montre une seule publication avec une entrée d’ID.
/composants/post.svelte
<script lang="ts">
export let post: Post;
</script>
<div class="border border-1 p-3 my-3">
<h1 class="text-xl underline hover:no-underline">
<a href={`/post/${post.id}`}>{post.title}</a>
</h1>
<small>By {post.author} - {new Date(post.created_at).toDateString()}</small>
<article>
{post.content}
</article>
</div>
Cela affiche les informations de base sur la publication.
/app.d.ts
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
type Post = {
id: number;
title: string;
content: string;
author: string;
created_at: string;
};
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {};
Ajoutez un type global pour la publication.
3. Créez les mécanismes de mise en cache
/lib/cache.ts
import { browser } from "$app/environment";
import { error } from "@sveltejs/kit";
export const cache = new Map();
export const cacheFetch = async <T>(
key: string,
fetchCallback: () => ReturnType<typeof fetch>
) => {
if (browser && cache.has(key)) {
return cache.get(key) as T
}
const response = await fetchCallback();
if (!response.ok) {
const message = await response.json();
throw error(response.status, message);
}
const result = await response.json();
cache.set(key, result);
return result as T;
};
Tout d’abord, il suffit d’exécuter le cache sur le browser
. Le cache lui-même n’est qu’un singleton Carte objet. Parce que vous l’importez dans vos fonctions de chargement, les données seront conservées sur le client. Le fichier n’est exécuté qu’une seule fois, gardant la carte intacte, tandis que la fonction cacheFetch
peut être exécuté plusieurs fois.
J’ai également décidé de simplifier le mécanisme de récupération. Répéter la vérification des erreurs et json()
techniques de réponse avec fetch
peut devenir fastidieux. Tout ce qu’il fait, c’est renvoyer un cache s’il existe, sinon récupérer les données, définir le cache et le renvoyer.
Note: Vous pourriez facilement vous débarrasser du fetch
passe-partout ici si vous utilisez un autre mécanisme comme Prisma, Firebase, Supabase, etc.
/routes/+page.ts
import type { PageLoad } from "./$types";
import { cacheFetch } from "$lib/cache";
export const load = (async ({ fetch, url }) => {
const { pathname } = url;
return {
posts: cacheFetch<Post[]>(pathname, () => fetch(`/data`))
};
}) satisfies PageLoad;
Regardez à quel point ce code est propre. Le cacheFetch
La fonction vous permet de transmettre le type de retour souhaité, et cela fonctionne. Vous pouvez utiliser fetch
comme vous le souhaitez avec toutes les options, car la fonction elle-même est un rappeler fonction. pathname
est utilisé comme clé pour le cache.
Vous pouvez également simplement retourner le fetch
ou cacheFetch
dans ce cas sans await
comme le fera SvelteKit fetch
pour vous avec toutes les autres fonctions de chargement. Notez que nous utilisons également +page.ts
au lieu de +page.server.ts
car cela s’exécute à la fois sur le client et sur le serveur.
/itinéraires/poste/urn:uuid:304ed28e-4911-41ef-8a8e-e0879d531a38/+page.ts
import type { PageLoad } from "./$types";
import { cacheFetch } from "$lib/cache";
export const load = (async ({ fetch, params, url }) => {
const { id } = params;
const { pathname } = url;
return {
post: cacheFetch<Post>(pathname, () => fetch(`/data?id=${id}`))
};
}) satisfies PageLoad;
Cela charge les données pour la publication individuelle. Vous obtenez le id
du dossier de routage basé sur des fichiers urn:uuid:304ed28e-4911-41ef-8a8e-e0879d531a38
. Si vous entrez un mauvais id
cela générera une erreur comme prévu.
Résultat final
La première fois que vous chargez une page, vous voyez une icône de chargement. Lors des demandes ultérieures, il se charge immédiatement. C’est le pouvoir de la mise en cache. Maintenant, garde à l’esprit que ça commence réellement prélecture lorsque vous survolez le lien lui-même, pas lorsque vous cliquez dessus. C’est l’une des grandes choses de SvelteKit.
Une dernière étape
Maintenant, nous pourrions aller plus loin en mettant en cache toutes les publications lors de la récupération initiale. Étant donné que nous recevons en fait tous les messages depuis le début, il n’y a aucune raison de les récupérer à nouveau.
import type { PageLoad } from "./$types";
import { cache, cacheFetch } from "$lib/cache";
export const load = (async ({ fetch, url }) => {
const { pathname } = url;
const posts = await cacheFetch<Post[]>(pathname, () => fetch(`/data`));
posts.map(post => cache.set(`${pathname}post/${post.id}`, post));
return {
posts
};
}) satisfies PageLoad;
Nous pouvons simplement parcourir toutes les publications téléchargées et les placer individuellement dans le cache avec map
et en important le cache
depuis cache.ts
directement.
Lorsque vous chargez votre page une fois, vous n’avez jamais besoin de récupérer quoi que ce soit d’autre ! Cela peut être utile pour prévenir Problème de requête N+1.
Ce n’est pas toujours une solution. Par exemple, si la page de détail de votre publication contient plus d’informations que la page de liste de publications. Vous devez décider si une récupération excessive la première fois vaut la peine de ne pas récupérer du tout sur les pages de détails des publications.
Quoi qu’il en soit, vous disposez désormais d’options de mise en cache client et vous savez comment implémenter les vôtres !
Dépôt : GitHub
Source link