Adatok gyorsítótárazása a SvelteKitben

Adatok gyorsítótárazása a SvelteKitben

My előző poszt volt egy átfogó áttekintés a SvelteKitről, ahol láthattuk, milyen nagyszerű eszköz a webfejlesztéshez. Ez a bejegyzés felvázolja, amit ott csináltunk, és belemerül minden fejlesztő kedvenc témájába: caching. Szóval mindenképpen olvasd el az utolsó bejegyzésemet, ha még nem tetted meg. A bejegyzés kódja elérhető a GitHubon<p></p> élő demó.

Ez a bejegyzés az adatkezelésről szól. Hozzáadunk néhány kezdetleges keresési funkciót, amely módosítja az oldal lekérdezési karakterláncát (a SvelteKit beépített funkcióival), és újraindítja az oldal betöltőjét. De ahelyett, hogy újra lekérdeznénk a (képzeletbeli) adatbázisunkat, hozzáadunk némi gyorsítótárat, így a korábbi keresések újrakeresése (vagy a vissza gomb használatával) gyorsan, a gyorsítótárból megjeleníti a korábban letöltött adatokat. Megvizsgáljuk, hogyan szabályozható a gyorsítótárazott adatok érvényességének időtartama, és ami még fontosabb, hogyan lehet manuálisan érvényteleníteni az összes gyorsítótárazott értéket. Hab a tortán megnézzük, hogyan tudjuk manuálisan frissíteni az adatokat az aktuális képernyőn, kliens oldalon egy mutáció után, a gyorsítótár törlése mellett.

Ez egy hosszabb, nehezebb bejegyzés lesz, mint a legtöbb, amit általában írok, mivel nehezebb témákkal foglalkozunk. Ez a bejegyzés lényegében megmutatja, hogyan valósíthatja meg a népszerű adatszolgáltatások, például react-query; de ahelyett, hogy külső könyvtárat vennénk igénybe, csak a webes platformot és a SvelteKit szolgáltatásait fogjuk használni.

Sajnos a webes platform szolgáltatásai valamivel alacsonyabb szintűek, így kicsit több munkát fogunk végezni, mint ahogy azt Ön megszokhatta. A másik oldal az, hogy nem lesz szükségünk külső könyvtárakra, ami segít megőrizni a csomagméreteket szép és kicsiben. Kérem, ne használja az általam bemutatott megközelítéseket, hacsak nincs rá jó oka. A gyorsítótárazás könnyen tévedhet, és amint látni fogja, egy kis bonyolultság az alkalmazáskódot eredményezi. Remélhetőleg az adattár gyors, és a felhasználói felülete rendben van, így a SvelteKit mindig lekérheti az adott oldalhoz szükséges adatokat. Ha igen, hagyd békén. Élvezze az egyszerűséget. De ez a bejegyzés megmutat néhány trükköt, amikor ez megszűnik.

Apropó react-query, ez éppen szabadult Svelte számára! Tehát ha a kézi gyorsítótárazási technikákra támaszkodik nagyon, feltétlenül nézze meg a projektet, és nézze meg, segíthet-e.

Felállítása

Mielőtt elkezdenénk, hajtsunk végre néhány apró változtatást azt a kódot, ami korábban volt. Ez ürügyet ad arra, hogy megnézzünk néhány más SvelteKit-funkciót, és ami még fontosabb, felkészít minket a sikerre.

Először helyezzük át az adatbetöltésünket a betöltőnkből +page.server.js egy API útvonal. Létrehozunk a +server.js fájl routes/api/todos, majd adjon hozzá egy GET funkció. Ez azt jelenti, hogy mostantól letölthetjük (az alapértelmezett GET ige használatával) a /api/todos pálya. Ugyanazt az adatbetöltési kódot adjuk hozzá, mint korábban.

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);
}

Ezután vegyük a meglévő oldalbetöltőt, és egyszerűen nevezzük át a fájlt +page.server.js nak nek +page.js (Vagy .ts ha a projektjét TypeScript használatára állította fel). Ezzel a betöltőnk „univerzális” betöltővé válik, nem pedig szerverbetöltővé. A SvelteKit dokumentumok magyarázza el a különbséget, de egy univerzális betöltő fut a szerveren és a kliensen is. Egyik előnyünk az, hogy a fetch Az új végpontunk hívása közvetlenül a böngészőnkből fog futni (a kezdeti betöltés után), a böngésző natív használatával fetch funkció. Kissé hozzáadjuk a szabványos HTTP-gyorsítótárat, de egyelőre csak a végpontot hívjuk meg.

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, };
}

Most adjunk hozzá egy egyszerű űrlapot /list oldal:

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

Igen, az űrlapok közvetlenül a normál oldalbetöltőinket célozhatják meg. Most hozzáadhatunk egy keresőkifejezést a keresőmezőbe, nyomjuk meg belép, és az URL lekérdezési karakterláncához hozzá lesz fűzve egy „keresési” kifejezés, amely újra futtatja a betöltőnket, és keresni fog a teendőink között.

Keresés űrlap
Adatok gyorsítótárazása a SvelteKitben

Növeljük a késedelmet is todoData.js fájl /lib/data. Ez megkönnyíti annak megállapítását, hogy mikor vannak az adatok gyorsítótárban, és mikor nem, miközben dolgozunk ezen a bejegyzésen.

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

Ne feledje, ennek a bejegyzésnek a teljes kódja mindezt a GitHubon, ha hivatkoznia kell rá.

Alapvető gyorsítótár

Kezdjük azzal, hogy adjunk hozzá néhány gyorsítótárat /api/todos végpont. Visszatérünk a sajátunkhoz +server.js fájlt, és adja hozzá az első cache-control fejlécünket.

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

…amitől az egész függvény így fog kinézni:

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);
}

Hamarosan megvizsgáljuk a kézi érvénytelenítést, de ez a funkció csak annyit mond, hogy ezeket az API-hívásokat 60 másodpercig gyorsítótárazza. Állítsd ezt arra, amire akarodés a használati esettől függően stale-while-revalidate is érdemes lehet utánanézni.

És éppen így, a lekérdezéseink gyorsítótárazásban vannak.

Gyorsítótár a DevToolsban.
Adatok gyorsítótárazása a SvelteKitben

Megjegyzések győződjön meg róla szüntesse meg a bejelölést a jelölőnégyzet, amely letiltja a gyorsítótárazást a fejlesztői eszközökben.

Ne feledje, ha az alkalmazásban a kezdeti navigáció a listaoldal, akkor ezek a keresési eredmények a SvelteKit belső gyorsítótárában lesznek tárolva, ezért ne számítson arra, hogy bármit is látni fog a DevToolsban, amikor visszatér a kereséshez.

Mi van gyorsítótárban és hol

Az alkalmazásunk legelső, szerver által megjelenített betöltése (feltételezve, hogy a /list oldal) lesz letöltve a szerveren. A SvelteKit szerializálja és elküldi ezeket az adatokat ügyfelünknek. Mi több, be fogja tartani a Cache-Control header a válaszon, és tudni fogja, hogy ezeket a gyorsítótárazott adatokat kell használni az adott végponti híváshoz a gyorsítótár ablakban (amit 60 másodpercre állítottunk be a put példában).

A kezdeti betöltés után, amikor elkezdi a keresést az oldalon, látnia kell a böngészőtől érkező hálózati kéréseket /api/todos lista. Miközben olyan dolgokat keres, amelyeket már keresett (az elmúlt 60 másodpercben), a válaszoknak azonnal be kell tölteniük, mivel a gyorsítótárban vannak.

Ami ebben a megközelítésben különösen jó, az az, hogy mivel ez a böngésző natív gyorsítótárazásán keresztül történik, ezek a hívások (attól függően, hogy hogyan kezeli a gyorsítótár-letörést, amelyet megvizsgálunk) továbbra is gyorsítótárban maradhatnak, még akkor is, ha újratölti az oldalt (ellentétben a kezdeti szerveroldali betöltés, amely mindig frissen hívja a végpontot, még akkor is, ha az utolsó 60 másodpercben tette meg).

Nyilvánvaló, hogy az adatok bármikor változhatnak, ezért szükségünk van egy módra a gyorsítótár manuális kiürítésére, amelyet a következő lépésben nézünk meg.

Gyorsítótár érvénytelenítése

Jelenleg az adatok 60 másodpercig gyorsítótárban lesznek. Bármi legyen is, egy perc múlva friss adatok kerülnek ki az adattárunkból. Lehet, hogy rövidebb vagy hosszabb időtartamot szeretne, de mi történik, ha néhány adatot mutál, és azonnal törli a gyorsítótárat, hogy a következő lekérdezése naprakész legyen? Ezt úgy oldjuk meg, hogy hozzáadunk egy lekérdezés-busting értéket az új címünkre küldött URL-hez /todos végpont.

Tároljuk ezt a cache-busting értéket egy cookie-ban. Ez az érték beállítható a szerveren, de továbbra is olvasható a kliensen. Nézzünk néhány mintakódot.

Létrehozhatunk a +layout.server.js fájl a mi gyökerünkben routes mappát. Ez az alkalmazás indításakor futni fog, és tökéletes hely a cookie kezdeti értékének beállítására.

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, };
}

Lehet, hogy észrevette a isDataRequest érték. Ne feledje, hogy az elrendezések bármikor újrafutnak, amikor ügyfélkódot hívnak invalidate(), vagy amikor szerverműveletet futtatunk (feltételezve, hogy nem kapcsoljuk ki az alapértelmezett viselkedést). isDataRequest jelzi az újrafuttatásokat, ezért csak akkor állítjuk be a cookie-t, ha az false; ellenkező esetben azt küldjük, ami már ott van.

A httpOnly: false zászló is jelentős. Ez lehetővé teszi, hogy ügyfélkódunk beolvassa ezeket a cookie-értékeket document.cookie. Ez általában biztonsági aggodalomra ad okot, de esetünkben ezek értelmetlen számok, amelyek lehetővé teszik a gyorsítótárba helyezést vagy a gyorsítótár-kiesést.

A gyorsítótár értékeinek olvasása

Az univerzális rakodógépünk a miénk /todos végpont. Ez a szerveren vagy a kliensen fut, és be kell olvasnunk azt a gyorsítótár-értéket, amelyet éppen beállítottunk, függetlenül attól, hogy hol vagyunk. Viszonylag egyszerű, ha a szerveren vagyunk: hívhatjuk await parent() hogy megkapja az adatokat a szülő elrendezésekből. De a kliensnél némi bruttó kódot kell használnunk az elemzéshez 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] ?? "";
};

Szerencsére csak egyszer kell.

A gyorsítótár értékének kiküldése

De most muszáj küld ez az érték számunkra /todos végpont.

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') bejelöli, hogy a kliensben vagyunk-e (ellenőrizve a dokumentum típusát), és nem ad vissza semmit, ha igen, ekkor tudjuk, hogy a szerveren vagyunk. Ezután az elrendezésünk értékét használja.

A gyorsítótár feltörése

De hogyan valóban frissítjük ezt a gyorsítótár-leállási értéket, amikor szükségünk van rá? Mivel cookie-ban van tárolva, bármely szerverműveletből így hívhatjuk:

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

Az implementáció

Innen már minden lefelé; elvégeztük a kemény munkát. Leírtuk a különféle webes platform-primitíveket, amelyekre szükségünk van, valamint azt is, hogy hol tartanak. Most pedig szórakozzunk egy kicsit, és írjunk alkalmazáskódot, hogy mindezt összekapcsoljuk.

A rövid időn belül világossá váló okok miatt kezdjük egy szerkesztési funkció hozzáadásával /list oldalon. Minden feladathoz hozzáadjuk ezt a második táblázatsort:

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>

És természetesen hozzá kell adnunk egy űrlapműveletet is /list oldalon. A cselekvések csak beindulhatnak .server oldalt, így hozzáadunk egy +page.server.js miénkben /list mappát. (Igen, egy +page.server.js fájl együtt létezhet a mellett +page.js fájl.)

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 }); },
};

Megragadjuk az űrlapadatokat, késleltetést kényszerítünk ki, frissítjük a teendőnket, majd, ami a legfontosabb, kiürítjük a gyorsítótár buktatóit.

Tegyünk egy próbát. Töltse be újra az oldalt, majd szerkessze az egyik teendőt. Egy pillanat múlva látnia kell a táblázat értékének frissítését. Ha megnézi a DevToold Hálózat lapját, látni fog egy lekérést /todos végpont, amely visszaadja az új adatokat. Egyszerű, és alapértelmezés szerint működik.

Adatok mentése
Adatok gyorsítótárazása a SvelteKitben

Azonnali frissítések

Mi van, ha el akarjuk kerülni azt a lekérést, amely a teendőink frissítése után történik, és ehelyett a módosított elemet közvetlenül a képernyőn frissítjük?

Ez nem csak a teljesítmény kérdése. Ha rákeres a „bejegyzés” szóra, majd eltávolítja a „bejegyzés” szót a lista bármely teendőjéből, a szerkesztés után azok eltűnnek a listáról, mivel már nem szerepelnek az adott oldal keresési eredményei között. Az UX-t jobbá tehetné egy ízléses animációval az izgalmas feladatokhoz, de tegyük fel, hogy szerettük volna nem futtassa újra az oldal betöltési funkcióját, de továbbra is törölje a gyorsítótárat, és frissítse a módosított teendőket, hogy a felhasználó lássa a szerkesztést. A SvelteKit ezt lehetővé teszi – lássuk, hogyan!

Először is hajtsunk végre egy kis változtatást a rakodógépünkön. A teendőink visszaküldése helyett adjuk vissza a írható bolt tartalmazza a teendőinket.

return { todos: writable(todos),
};

Korábban hozzáfértünk a teendőinkhez a data prop, amely nem a mi tulajdonunk, és nem tudjuk frissíteni. De a Svelte megengedi, hogy visszaadjuk adatainkat a saját áruházukban (feltételezve, hogy univerzális betöltőt használunk, mi is az). Csak még egy finomítást kell végrehajtanunk /list cimre.

Ehelyett:

{#each todos as t}

…ezt meg kell tennünk azóta todos már maga is egy bolt.:

{#each $todos as t}

Adataink most ugyanúgy betöltődnek, mint korábban. De azóta todos írható üzlet, frissíthetjük.

Először is biztosítsunk egy funkciót use:enhance tulajdonság:

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

Ez a beküldés előtt fut le. A következőt írjuk:

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; } }) ); };
}

Ez a funkció biztosítja a data objektum űrlapadatainkkal. Mi visszatérés egy aszinkron függvény, amely futni fog után szerkesztésünk kész. A dokik magyarázza el mindezt, de ezzel kikapcsoljuk a SvelteKit alapértelmezett űrlapkezelését, amely újra futtatta volna a betöltőnket. Pontosan ezt akarjuk! (Könnyen visszaállíthatjuk ezt az alapértelmezett viselkedést, amint azt a dokumentumok elmagyarázzák.)

Most hívjuk update miénken todos tömb, mivel ez egy bolt. És ennyi. Egy teendő szerkesztése után azonnal megjelennek a módosításaink, és törlődik a gyorsítótárunk (mint korábban, mivel új cookie-értéket adtunk meg editTodo form akció). Tehát, ha keresünk, majd visszanavigálunk erre az oldalra, akkor friss adatokat kapunk a betöltőnktől, amely helyesen kizárja a frissített teendőket.

Az azonnali frissítés kódja elérhető a GitHubon.

Mélyebbre ásni

A cookie-kat bármilyen szerver betöltési funkcióban (vagy szerverműveletben) beállíthatjuk, nem csak a gyökérelrendezésben. Tehát, ha bizonyos adatokat csak egyetlen elrendezés vagy akár egyetlen oldal alatt használnak fel, akkor ott beállíthatja a cookie értékét. Ráadásul, ha az vagy nem Az imént bemutatott trükköt követve a képernyőn megjelenő adatok kézi frissítését mutattam be, és ehelyett szeretném, ha a betöltő újrafutna egy mutáció után, akkor mindig beállíthat egy új cookie-értéket közvetlenül a betöltési funkcióban anélkül, hogy ellenőrzött volna. isDataRequest. Kezdetben beállítja, majd bármikor, amikor egy szerverműveletet futtat, az oldalelrendezés automatikusan érvényteleníti és újra hívja a betöltőt, újra beállítva a cache bust karakterláncot az univerzális betöltő meghívása előtt.

Újratöltési függvény írása

Végezetül építsünk egy utolsó funkciót: egy újratöltés gombot. Adjunk a felhasználóknak egy gombot, amely törli a gyorsítótárat, majd újratölti az aktuális lekérdezést.

Hozzáadunk egy piszok egyszerű űrlapműveletet:

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

Valós projektben valószínűleg nem másolná ki/illesztené be ugyanazt a kódot, hogy ugyanazt a cookie-t több helyre állítsa be, de ennél a bejegyzésnél az egyszerűség és az olvashatóság érdekében optimalizálunk.

Most hozzunk létre egy űrlapot, amelyen közzétehetjük:

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

Működik!

UI újratöltés után.
Adatok gyorsítótárazása a SvelteKitben

Nevezhetnénk ezt késznek és továbblépnénk, de javítsuk egy kicsit ezen a megoldáson. Pontosabban, adjunk visszajelzést az oldalon, hogy közöljük a felhasználóval, hogy az újratöltés megtörténik. Ezenkívül alapértelmezés szerint a SvelteKit-műveletek érvénytelenek minden. Az aktuális oldal hierarchiájában minden elrendezés, oldal stb. újratöltődne. Lehetnek olyan adatok, amelyek egyszer betöltődnek a gyökérelrendezésben, amelyeket nem kell érvénytelenítenünk vagy újra betöltenünk.

Tehát fókuszáljunk egy kicsit a dolgokra, és csak akkor töltsük újra a teendőinket, amikor meghívjuk ezt a függvényt.

Először adjunk át egy függvényt a javításhoz:

<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; }); };
};

Újat állítunk be reloading változó -ra true a kezdet ennek az akciónak. Ezután annak érdekében, hogy felülbírálhassuk az alapértelmezett viselkedést, amely mindent érvénytelenít, visszaadunk egy async funkció. Ez a funkció akkor fog futni, amikor a szerverműveletünk befejeződik (ami csak egy új cookie-t állít be).

E nélkül async függvény visszaadta, a SvelteKit mindent érvénytelenítene. Mivel mi biztosítjuk ezt a funkciót, semmit sem fog érvényteleníteni, így rajtunk múlik, hogy megmondjuk, mit töltsön újra. Ezt a invalidate funkció. értékkel hívjuk reload:todos. Ez a függvény ígéretet ad vissza, amely az érvénytelenítés befejeztével oldódik meg, és ekkor állítjuk be reloading vissza false.

Végül szinkronizálnunk kell a betöltőnket ezzel az újjal reload:todos érvénytelenítési érték. Ezt a rakodónkban a depends funkció:

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

És ennyi. depends és a invalidate hihetetlenül hasznos funkciók. Az a menő invalidate nem csak az általunk biztosított önkényes értékeket veszi fel, mint mi. Megadhatunk egy URL-t is, amelyet a SvelteKit nyomon követ, és érvényteleníti az ettől az URL-től függő betöltőket. Ebből a célból, ha arra kíváncsi, hogy kihagyhatnánk-e a hívást depends és érvénytelenítse a mi /api/todos végpontot összesen, megteheti, de meg kell adnia a pontos URL, beleértve a search kifejezést (és a gyorsítótár értékét). Tehát összeállíthatja az URL-t az aktuális kereséshez, vagy egyeznie kell az elérési út nevével, például:

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

Személy szerint én megtalálom a használható megoldást depends egyértelműbb és egyszerűbb. De lásd a dokik persze további információkért, és döntsd el magad.

Ha szeretné látni az újratöltés gombot működés közben, akkor a kód benne van a repó ezen ága.

Elválás gondolatok

Ez egy hosszú bejegyzés volt, de remélhetőleg nem elsöprő. A SvelteKit használata során különféle módokon dolgoztunk az adatok gyorsítótárazására. Ennek nagy része csupán arról szólt, hogy webes platform primitíveket használtunk a megfelelő gyorsítótár és cookie-értékek hozzáadásához, amelyek ismerete általában a webfejlesztésben is szolgálni fog, a SvelteKit mellett.

Sőt, ez az, amit feltétlenül te nem kell állandóan. Vitathatatlan, hogy csak akkor nyúljon az efféle speciális funkciókhoz, amikor valójában szükségük van rájuk. Ha az adattár gyorsan és hatékonyan szolgálja ki az adatokat, és nem kell semmiféle skálázási problémával küzdenie, akkor nincs értelme az alkalmazáskódot fölösleges bonyolultsággal felduzzasztani, és elvégezni azokat a dolgokat, amelyekről itt beszéltünk.

Mint mindig, írjon világos, tiszta, egyszerű kódot, és szükség esetén optimalizálja. Ennek a bejegyzésnek az volt a célja, hogy ezeket az optimalizálási eszközöket biztosítsa Önnek, amikor valóban szüksége van rájuk. Remélem élvezted!

Időbélyeg:

Még több CSS trükkök