Predpomnjenje podatkov v SvelteKit

Predpomnjenje podatkov v SvelteKit

My Prejšnja objava je bil širok pregled SvelteKita, kjer smo videli, kako odlično orodje je za spletni razvoj. Ta objava bo razkrila, kar smo naredili tam, in se poglobila v najljubšo temo vsakega razvijalca: predpomnjenje. Torej, ne pozabite prebrati moje zadnje objave, če je še niste. Koda za to objavo je na voljo na GitHubu, Pa tudi demo v živo.

Ta objava govori o ravnanju s podatki. Dodali bomo nekaj osnovne funkcije iskanja, ki bo spremenila poizvedbeni niz strani (z uporabo vgrajenih funkcij SvelteKit) in ponovno sprožila nalagalnik strani. Toda namesto da samo ponovno poizvedujemo po naši (namišljeni) zbirki podatkov, bomo dodali nekaj predpomnjenja, tako da bo ponovno iskanje prejšnjih iskanj (ali uporaba gumba za nazaj) hitro pokazalo predhodno pridobljene podatke iz predpomnilnika. Ogledali si bomo, kako nadzorovati čas, v katerem predpomnjeni podatki ostanejo veljavni, in, kar je še pomembneje, kako ročno razveljaviti vse predpomnjene vrednosti. Kot češnjo na torti si bomo ogledali, kako lahko ročno posodobimo podatke na trenutnem zaslonu, na strani odjemalca, po mutaciji, medtem ko še vedno čistimo predpomnilnik.

To bo daljša in težja objava kot večina tega, kar običajno pišem, saj obravnavamo težje teme. Ta objava vam bo v bistvu pokazala, kako implementirati skupne funkcije priljubljenih podatkovnih pripomočkov, kot je odzivna poizvedba; vendar namesto zunanje knjižnice bomo uporabljali samo spletno platformo in funkcije SvelteKit.

Na žalost so funkcije spletne platforme na nekoliko nižji ravni, zato bomo opravili nekoliko več dela, kot ste morda vajeni. Dobra stran je, da ne bomo potrebovali nobenih zunanjih knjižnic, kar bo pripomoglo k temu, da bodo velikosti paketov lepe in majhne. Prosim, ne uporabljajte pristopov, ki vam jih bom pokazal, razen če imate za to dober razlog. Pri predpomnjenju se je zlahka zmotiti in kot boste videli, bo koda vaše aplikacije nekoliko zapletena. Upajmo, da je vaša shramba podatkov hitra in da je vaš uporabniški vmesnik v redu, saj SvelteKitu omogoča, da vedno zahteva podatke, ki jih potrebuje za katero koli stran. Če je, ga pusti pri miru. Uživajte v preprostosti. Toda ta objava vam bo pokazala nekaj trikov, ko temu ne bo več tako.

Ko že govorimo o odzivni poizvedbi, je je bil pravkar izdan za Svelte! Če se torej zanašate na tehnike ročnega predpomnjenja veliko, obvezno preverite ta projekt in preverite, ali bi lahko pomagal.

Postavitev

Preden začnemo, naredimo nekaj majhnih sprememb kodo, ki smo jo imeli prej. To nam bo dalo izgovor, da vidimo nekatere druge funkcije SvelteKit in, kar je še pomembneje, nas bo pripravilo na uspeh.

Najprej premaknimo naše nalaganje podatkov iz našega nalagalnika +page.server.js v Pot API. Ustvarili bomo a +server.js datoteka v routes/api/todos, in nato dodajte a GET funkcijo. To pomeni, da bomo zdaj lahko pridobili (z uporabo privzetega glagola GET) v /api/todos pot. Dodali bomo isto kodo za nalaganje podatkov kot prej.

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

Nato vzemimo nalagalnik strani, ki smo ga imeli, in preprosto preimenujmo datoteko +page.server.js do +page.js (ali .ts če ste svoj projekt prilagodili uporabi TypeScript). To spremeni naš nalagalnik tako, da postane "univerzalni" nalagalnik in ne nalagalnik strežnika. Dokumenti SvelteKit razložite razliko, vendar univerzalni nalagalnik deluje na strežniku in tudi na odjemalcu. Ena prednost za nas je, da fetch klic v našo novo končno točko se bo izvajal neposredno iz našega brskalnika (po začetnem nalaganju), z uporabo izvornega brskalnika fetch funkcijo. Čez nekaj časa bomo dodali standardno predpomnjenje HTTP, a zaenkrat bomo samo poklicali končno točko.

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

Zdaj pa našemu dodamo preprost obrazec /list Stran:

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

Ja, obrazci lahko ciljajo neposredno na naše običajne nalagalnike strani. Zdaj lahko dodamo iskalni izraz v iskalno polje, zadetek Vnesite, poizvedbenemu nizu URL-ja pa bo dodan izraz »iskanje«, ki bo ponovno zagnal naš nakladalnik in preiskal naše elemente opravil.

Iskalnik
Predpomnjenje podatkov v SvelteKit

Povečajmo tudi zamudo pri naših todoData.js datoteka v /lib/data. To bo olajšalo pregled nad tem, kdaj so podatki predpomnjeni in kdaj niso, ko delamo s to objavo.

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

Ne pozabite, da je celotna koda za to objavo vse na GitHubu, če se morate sklicevati nanj.

Osnovno predpomnjenje

Začnimo z dodajanjem predpomnjenja našemu /api/todos končna točka. Vrnili se bomo k našim +server.js datoteko in dodamo našo prvo glavo za nadzor predpomnilnika.

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

... zaradi česar bo celotna funkcija videti takole:

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

Kmalu si bomo ogledali ročno razveljavitev, vendar ta funkcija pravi, da predpomni te klice API-ja za 60 sekund. To nastavite na karkoli želitein glede na vaš primer uporabe, stale-while-revalidate bi bilo prav tako vredno pogledati.

In ravno tako se naše poizvedbe predpomnijo.

Predpomnilnik v DevTools.
Predpomnjenje podatkov v SvelteKit

Opombe prepričaj se da odkljukajte potrditveno polje, ki onemogoči predpomnjenje v orodjih za razvijalce.

Ne pozabite, da če je vaša začetna navigacija v aplikaciji stran s seznamom, bodo ti rezultati iskanja interno predpomnjeni v SvelteKitu, zato ne pričakujte, da boste videli kaj v DevTools, ko se vrnete na to iskanje.

Kaj je predpomnjeno in kje

Naše prvo strežniško upodobljeno nalaganje naše aplikacije (ob predpostavki, da začnemo pri /list stran) bodo prenesene na strežnik. SvelteKit bo te podatke serializiral in poslal naši stranki. Še več, opazoval bo Cache-Control glava na odgovor in bo vedel uporabiti te predpomnjene podatke za ta klic končne točke v oknu predpomnilnika (ki smo ga v primeru postavitve nastavili na 60 sekund).

Po tem začetnem nalaganju, ko začnete iskati po strani, bi morali videti omrežne zahteve iz vašega brskalnika v /api/todos seznam. Ko iščete stvari, ki ste jih že iskali (v zadnjih 60 sekundah), bi se morali odgovori naložiti takoj, saj so shranjeni v predpomnilniku.

Pri tem pristopu je še posebej kul to, da se lahko ti klici (odvisno od tega, kako upravljate z uničenjem predpomnilnika, ki si ga bomo ogledali) še naprej predpomnijo, tudi če znova naložite stran (za razliko od začetno nalaganje na strani strežnika, ki končno točko vedno pokliče sveže, tudi če je to storila v zadnjih 60 sekundah).

Očitno se lahko podatki kadar koli spremenijo, zato potrebujemo način za ročno čiščenje tega predpomnilnika, ki si ga bomo ogledali v nadaljevanju.

Razveljavitev predpomnilnika

Trenutno bodo podatki shranjeni v predpomnilniku za 60 sekund. Ne glede na vse bodo čez minuto iz naše podatkovne shrambe potegnjeni sveži podatki. Morda boste želeli krajše ali daljše časovno obdobje, toda kaj se zgodi, če mutirate nekaj podatkov in želite takoj počistiti predpomnilnik, da bo vaša naslednja poizvedba posodobljena? To bomo rešili tako, da URL-ju, ki ga pošljemo našemu novemu, dodamo vrednost za razbijanje poizvedb /todos končna točka.

Shranimo to vrednost prekinitve predpomnilnika v piškotek. To vrednost je mogoče nastaviti na strežniku, vendar jo še vedno prebrati na odjemalcu. Poglejmo nekaj vzorčne kode.

Ustvarimo lahko a +layout.server.js datoteko v samem korenu našega routes mapo. To se bo zagnalo ob zagonu aplikacije in je odlično mesto za nastavitev začetne vrednosti piškotka.

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

Morda ste opazili isDataRequest vrednost. Ne pozabite, da se bodo postavitve znova zagnale kadar koli kliče koda odjemalca invalidate(), ali kadar koli izvedemo dejanje strežnika (ob predpostavki, da ne izklopimo privzetega vedenja). isDataRequest označuje te ponovne zagone, zato nastavimo piškotek le, če je tako false; v nasprotnem primeru pošljemo kar je že tam.

O httpOnly: false pomembna je tudi zastava. To omogoča kodi naše stranke, da prebere te vrednosti piškotkov document.cookie. To bi običajno predstavljalo varnostno skrb, vendar so v našem primeru to nesmiselne številke, ki nam omogočajo predpomnjenje ali razpad predpomnilnika.

Branje vrednosti predpomnilnika

Naš univerzalni nakladalnik je tisto, kar kliče naš /todos končna točka. To se izvaja na strežniku ali odjemalcu in prebrati moramo to vrednost predpomnilnika, ki smo jo pravkar nastavili, ne glede na to, kje smo. Če smo na strežniku, je relativno enostavno: lahko pokličemo await parent() da pridobite podatke iz nadrejenih postavitev. Toda na odjemalcu bomo morali uporabiti nekaj grobe kode za razčlenjevanje 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] ?? "";
};

Na srečo ga potrebujemo samo enkrat.

Pošiljanje vrednosti predpomnilnika

Toda zdaj moramo pošljite ta vrednost za naše /todos končna točka.

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') v njem preveri, ali smo na odjemalcu (s preverjanjem vrste dokumenta), in ne vrne ničesar, če smo, na tej točki vemo, da smo na strežniku. Nato uporabi vrednost iz naše postavitve.

Razbijanje predpomnilnika

Ampak kako ali dejansko posodobimo to vrednost prekinitve predpomnilnika, ko jo potrebujemo? Ker je shranjen v piškotku, ga lahko pokličemo takole iz katerega koli dejanja strežnika:

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

Izvajanje

Od tod je vse navzdol; opravili smo težko delo. Pokrili smo različne primitive spletnih platform, ki jih potrebujemo, pa tudi, kam gredo. Zdaj pa se malo zabavajmo in napišimo kodo aplikacije, da vse skupaj povežemo.

Zaradi razlogov, ki bodo čez nekaj časa jasni, začnimo z dodajanjem funkcije urejanja v naše /list strani. To drugo vrstico tabele bomo dodali za vsako opravilo:

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>

In seveda bomo morali dodati dejanje obrazca za naše /list strani. Dejanja so lahko samo notranja .server strani, zato bomo dodali a +page.server.js v našem /list mapo. (Da, a +page.server.js datoteka lahko obstaja poleg a +page.js mapa.)

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

Zagrabimo podatke iz obrazca, vsilimo zakasnitev, posodobimo naša opravila in nato, kar je najpomembneje, izbrišemo piškotek za prekinitev predpomnilnika.

Poskusimo to. Znova naložite svojo stran in nato uredite eno od opravil. Čez trenutek bi morali videti posodobljeno vrednost tabele. Če pogledate na zavihek Omrežje v orodju DevToold, boste videli prenos v /todos končna točka, ki vrne vaše nove podatke. Enostavno in privzeto deluje.

Shranjevanje podatkov
Predpomnjenje podatkov v SvelteKit

Takojšnje posodobitve

Kaj pa, če se želimo izogniti pridobivanju, ki se zgodi, ko posodobimo svoj element opravila, in namesto tega posodobimo spremenjeni element kar na zaslonu?

To ni samo vprašanje uspešnosti. Če iščete »objavo« in nato odstranite besedo »objava« s katerega koli elementa opravil na seznamu, bodo po urejanju izginili s seznama, saj niso več v rezultatih iskanja na tej strani. UX bi lahko izboljšali z nekaj okusne animacije za razburljiva opravila, a recimo, da smo želeli ne znova zaženite funkcijo nalaganja te strani, vendar še vedno počistite predpomnilnik in posodobite spremenjena opravila, da lahko uporabnik vidi urejanje. SvelteKit to omogoča – poglejmo, kako!

Najprej naredimo eno majhno spremembo našega nakladalnika. Namesto da vrnemo naše opravke, vrnimo a pisljiva trgovina ki vsebuje naše naloge.

return { todos: writable(todos),
};

Prej smo do opravil dostopali na data prop, ki ni v naši lasti in ga ne moremo posodobiti. Toda Svelte nam dovoli, da naše podatke vrnemo v njihovo lastno trgovino (ob predpostavki, da uporabljamo univerzalni nalagalnik, kar tudi je). Narediti moramo samo še eno prilagoditev /list stran.

Namesto tega:

{#each todos as t}

… to moramo storiti od takrat todos je sama zdaj trgovina.:

{#each $todos as t}

Zdaj se naši podatki nalagajo kot prej. Toda odkar todos je pisljiva trgovina, jo lahko posodobimo.

Najprej zagotovimo funkcijo za našo use:enhance atribut:

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

To se bo zagnalo pred oddajo. Zapišimo naslednje:

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

Ta funkcija zagotavlja a data objekt z našimi podatki obrazca. mi vrnitev asinhrono funkcijo, ki se bo izvajala po naše urejanje je končano. Dokumenti pojasniti vse to, vendar s tem izklopimo privzeto obdelavo obrazcev SvelteKit, ki bi ponovno zagnala naš nalagalnik. To je točno to, kar si želimo! (To privzeto vedenje bi lahko preprosto vrnili nazaj, kot pojasnjujejo dokumenti.)

Zdaj kličemo update na našem todos niz, saj je trgovina. In to je to. Po urejanju elementa opravila se naše spremembe takoj prikažejo in naš predpomnilnik je počiščen (kot prej, saj smo v našem editTodo dejanje oblike). Torej, če iščemo in se nato pomaknemo nazaj na to stran, bomo dobili sveže podatke iz našega nalagalnika, ki bo pravilno izključil vse posodobljene elemente opravil, ki so bili posodobljeni.

Koda za takojšnje posodobitve je na voljo na GitHubu.

Kopanje globlje

Piškotke lahko nastavimo v kateri koli funkciji nalaganja strežnika (ali dejanju strežnika), ne le v korenski postavitvi. Torej, če se nekateri podatki uporabljajo samo pod eno samo postavitvijo ali celo eno stranjo, lahko to vrednost piškotka nastavite tam. Še več, če ste ne izvedete trik, ki sem ga pravkar pokazal, ročno posodabljanje podatkov na zaslonu in namesto tega želite, da se vaš nakladalnik po mutaciji znova zažene, potem lahko vedno nastavite novo vrednost piškotka kar v tej funkciji nalaganja brez kakršnega koli preverjanja isDataRequest. Na začetku se bo nastavil, nato pa bo vsakič, ko boste zagnali strežniško dejanje, ta postavitev strani samodejno razveljavila in znova priklicala vaš nalagalnik, pri čemer bo ponovno nastavljen niz za prekinitev predpomnilnika, preden bo poklican vaš univerzalni nalagalnik.

Pisanje funkcije za ponovno nalaganje

Zaključimo z izdelavo še zadnje funkcije: gumba za ponovno nalaganje. Dajmo uporabnikom gumb, ki bo počistil predpomnilnik in nato znova naložil trenutno poizvedbo.

Dodali bomo preprosto dejanje obrazca:

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

V resničnem projektu verjetno ne bi kopirali/prilepili iste kode za nastavitev istega piškotka na enak način na več mestih, vendar bomo za to objavo optimizirali za preprostost in berljivost.

Zdaj pa ustvarimo obrazec za objavo v njem:

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

To deluje!

UI po ponovnem nalaganju.
Predpomnjenje podatkov v SvelteKit

Temu bi lahko rekli, da je končano, in gremo naprej, vendar to rešitev nekoliko izboljšajmo. Natančneje, posredujmo povratne informacije na strani, da bi uporabniku povedali, da poteka ponovno nalaganje. Poleg tega so dejanja SvelteKit privzeto razveljavljena vse. Vsaka postavitev, stran itd. v hierarhiji trenutne strani bi se znova naložila. Morda so nekateri podatki, ki so enkrat naloženi v korenski postavitvi in ​​nam jih ni treba razveljaviti ali znova naložiti.

Torej, osredotočimo se na stvari in znova naložimo svoja opravila šele, ko pokličemo to funkcijo.

Najprej posredujmo funkcijo za izboljšanje:

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

Postavljamo novo reloading spremenljivka do true pri Začetek tega dejanja. In potem, da bi preglasili privzeto vedenje razveljavitve vsega, vrnemo an async funkcijo. Ta funkcija se bo zagnala, ko bo končano dejanje našega strežnika (ki samo nastavi nov piškotek).

Brez tega async funkcija vrnila, bi SvelteKit razveljavil vse. Ker zagotavljamo to funkcijo, ne bo ničesar razveljavila, zato je na nas, da ji povemo, kaj naj znova naloži. To počnemo z invalidate funkcijo. Imenujemo ga z vrednostjo reload:todos. Ta funkcija vrne obljubo, ki se razreši, ko je razveljavitev končana, na katero točko nastavimo reloading nazaj false.

Nazadnje moramo naš nalagalnik sinhronizirati s tem novim reload:todos vrednost razveljavitve. To naredimo v našem nakladalniku z depends funkcija:

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

In to je to. depends in invalidate so neverjetno uporabne funkcije. Kar je kul je to invalidate ne vzame samo poljubnih vrednosti, ki jih posredujemo, kot smo jih mi. Zagotovimo lahko tudi URL, ki mu bo SvelteKit sledil, in razveljavil vse nalagalnike, ki so odvisni od tega URL-ja. V ta namen, če se sprašujete, ali lahko preskočimo klic na depends in razveljavi naše /api/todos končno točko, lahko, vendar morate zagotoviti točno URL, vključno z search izraz (in našo vrednost predpomnilnika). Torej lahko sestavite URL za trenutno iskanje ali pa se ujemate z imenom poti, takole:

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

Osebno najdem rešitev, ki uporablja depends bolj eksplicitno in preprosto. Ampak glej dokumenti seveda za več informacij in se odločite sami.

Če želite videti gumb za ponovno nalaganje v akciji, je koda zanj to vejo repo.

Ločitvene misli

To je bila dolga objava, a upajmo, da ne preveč. Poglobili smo se v različne načine, kako lahko predpomnimo podatke, ko uporabljamo SvelteKit. Velik del tega je šlo le za uporabo primitivov spletne platforme za dodajanje pravilnega predpomnilnika in vrednosti piškotkov, katerih poznavanje vam bo služilo pri spletnem razvoju na splošno, ne le pri SvelteKitu.

Poleg tega je to nekaj, kar absolutno ne potrebujejo ves čas. Verjetno bi morali te vrste naprednih funkcij poseči le, ko jih dejansko potrebujejo. Če vaša podatkovna shramba streže podatke hitro in učinkovito in nimate opravka s kakršnimi koli težavami s skaliranjem, nima smisla napihnjevati kode aplikacije z nepotrebno zapletenostjo pri izvajanju stvari, o katerih smo govorili tukaj.

Kot vedno napišite jasno, čisto in preprosto kodo in po potrebi optimizirajte. Namen te objave je bil zagotoviti ta orodja za optimizacijo, ko jih resnično potrebujete. Upam, da ste uživali!

Časovni žig:

Več od Triki CSS