Kecerdasan Data Generatif

Caching Data di SvelteKit

Tanggal:

My sebelumnya pasca adalah gambaran luas tentang SvelteKit di mana kami melihat betapa hebatnya alat itu untuk pengembangan web. Posting ini akan mengakhiri apa yang kami lakukan di sana dan menyelami topik favorit setiap pengembang: caching. Jadi, pastikan untuk membaca posting terakhir saya jika Anda belum melakukannya. Kode untuk posting ini tersedia di GitHub, sebaik demo langsung.

Posting ini adalah semua tentang penanganan data. Kami akan menambahkan beberapa fungsi pencarian dasar yang akan memodifikasi string kueri halaman (menggunakan fitur SvelteKit bawaan), dan memicu ulang pemuat halaman. Namun, alih-alih hanya meminta ulang basis data (imajiner) kami, kami akan menambahkan beberapa caching sehingga pencarian ulang pencarian sebelumnya (atau menggunakan tombol kembali) akan menampilkan data yang diambil sebelumnya, dengan cepat, dari cache. Kita akan melihat cara mengontrol lamanya data yang di-cache tetap valid dan, yang lebih penting, cara membatalkan validasi semua nilai yang di-cache secara manual. Sebagai pelengkap, kita akan melihat bagaimana kita dapat memperbarui data secara manual di layar saat ini, sisi klien, setelah mutasi, sambil tetap membersihkan cache.

Ini akan menjadi posting yang lebih panjang dan lebih sulit daripada kebanyakan yang biasanya saya tulis karena kita membahas topik yang lebih sulit. Posting ini pada dasarnya akan menunjukkan kepada Anda bagaimana menerapkan fitur-fitur umum seperti utilitas data populer reaksi-permintaan; tetapi alih-alih menarik pustaka eksternal, kami hanya akan menggunakan platform web dan fitur SvelteKit.

Sayangnya, fitur platform web sedikit lebih rendah, jadi kami akan melakukan lebih banyak pekerjaan daripada yang biasa Anda lakukan. Keuntungannya adalah kita tidak memerlukan perpustakaan eksternal, yang akan membantu menjaga ukuran bundel tetap bagus dan kecil. Tolong jangan gunakan pendekatan yang akan saya tunjukkan kecuali Anda punya alasan bagus untuk itu. Caching mudah salah, dan seperti yang akan Anda lihat, ada sedikit kerumitan yang akan menghasilkan kode aplikasi Anda. Mudah-mudahan penyimpanan data Anda cepat, dan UI Anda baik-baik saja memungkinkan SvelteKit untuk selalu meminta data yang diperlukan untuk setiap halaman tertentu. Jika ya, biarkan saja. Nikmati kesederhanaannya. Tetapi posting ini akan menunjukkan kepada Anda beberapa trik ketika hal itu berhenti terjadi.

Berbicara tentang reaksi-permintaan, itu baru saja dirilis untuk Langsing! Jadi jika Anda menemukan diri Anda bersandar pada teknik caching manual banyak, pastikan untuk memeriksa proyek itu, dan lihat apakah itu dapat membantu.

Menyiapkan

Sebelum kita mulai, mari buat beberapa perubahan kecil pada kode yang kita miliki sebelumnya. Ini akan memberi kami alasan untuk melihat beberapa fitur SvelteKit lainnya dan, yang lebih penting, menyiapkan kami untuk sukses.

Pertama, mari kita pindahkan pemuatan data kita dari loader kita +page.server.js ke Rute API. Kami akan membuat +server.js file dalam routes/api/todos, lalu tambahkan a GET fungsi. Ini berarti kita sekarang dapat mengambil (menggunakan kata kerja GET default) ke /api/todos jalur. Kami akan menambahkan kode pemuatan data yang sama seperti sebelumnya.

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

Selanjutnya, mari ambil pemuat halaman yang kita miliki, dan cukup ganti nama file dari +page.server.js untuk +page.js (Atau .ts jika Anda telah merancah proyek Anda untuk menggunakan TypeScript). Ini mengubah loader kami menjadi loader "universal" daripada loader server. Dokumen SvelteKit jelaskan perbedaannya, tetapi pemuat universal berjalan di server dan juga klien. Satu keuntungan bagi kami adalah bahwa fetch panggilan ke titik akhir baru kami akan berjalan langsung dari browser kami (setelah pemuatan awal), menggunakan browser asli fetch fungsi. Kami akan menambahkan caching HTTP standar sebentar lagi, tetapi untuk saat ini, yang akan kami lakukan hanyalah memanggil titik akhir.

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

Sekarang mari tambahkan formulir sederhana ke /list Halaman:

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

Ya, formulir dapat menargetkan langsung ke pemuat halaman normal kami. Sekarang kita dapat menambahkan istilah pencarian di kotak pencarian, tekan Enter, dan istilah "telusuri" akan ditambahkan ke string kueri URL, yang akan menjalankan ulang loader kami dan menelusuri item tugas kami.

Formulir pencarian

Mari kita juga meningkatkan keterlambatan kita todoData.js file dalam /lib/data. Ini akan memudahkan untuk melihat kapan data ada dan tidak di-cache saat kami mengerjakan posting ini.

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

Ingat, kode lengkap untuk posting ini adalah semua di GitHub, jika Anda perlu merujuknya.

Caching dasar

Mari kita mulai dengan menambahkan beberapa caching ke /api/todos titik akhir. Kami akan kembali ke kami +server.js file dan tambahkan tajuk kontrol-cache pertama kami.

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

… yang akan membuat seluruh fungsi terlihat seperti ini:

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

Kami akan segera melihat pembatalan manual, tetapi semua fungsi ini mengatakan untuk meng-cache panggilan API ini selama 60 detik. Setel ini ke apa pun yang Anda inginkan, dan bergantung pada kasus penggunaan Anda, stale-while-revalidate mungkin juga layak untuk dilihat.

Dan begitu saja, kueri kami sedang di-cache.

Cache di DevTools.

Note pastikan Anda hapus centang kotak centang yang menonaktifkan caching di alat dev.

Ingat, jika navigasi awal Anda pada aplikasi adalah halaman daftar, hasil pencarian tersebut akan di-cache secara internal ke SvelteKit, jadi jangan berharap melihat apa pun di DevTools saat kembali ke pencarian itu.

Apa yang di-cache, dan di mana

Beban pertama aplikasi kami yang dirender oleh server (dengan asumsi kami mulai dari /list page) akan diambil di server. SvelteKit akan membuat serial dan mengirimkan data ini ke klien kami. Terlebih lagi, itu akan mengamati Cache-Control Header pada respons, dan akan tahu untuk menggunakan data yang di-cache ini untuk panggilan titik akhir di dalam jendela cache (yang kami setel ke 60 detik pada contoh).

Setelah pemuatan awal itu, ketika Anda mulai mencari di halaman, Anda akan melihat permintaan jaringan dari browser Anda ke /api/todos daftar. Saat Anda mencari hal-hal yang telah Anda cari (dalam 60 detik terakhir), respons akan segera dimuat karena di-cache.

Apa yang sangat keren dengan pendekatan ini adalah, karena ini melakukan caching melalui caching asli browser, panggilan ini dapat (bergantung pada bagaimana Anda mengelola penghancuran cache yang akan kita lihat) terus melakukan cache bahkan jika Anda memuat ulang halaman (tidak seperti pemuatan sisi server awal, yang selalu menyebut titik akhir baru, meskipun melakukannya dalam 60 detik terakhir).

Tentunya data dapat berubah kapan saja, jadi kita memerlukan cara untuk membersihkan cache ini secara manual, yang akan kita lihat selanjutnya.

Pembatalan cache

Saat ini, data akan di-cache selama 60 detik. Apa pun yang terjadi, setelah satu menit, data baru akan ditarik dari datastore kami. Anda mungkin menginginkan periode waktu yang lebih singkat atau lebih lama, tetapi apa yang terjadi jika Anda memutasikan beberapa data dan ingin segera menghapus cache agar kueri berikutnya diperbarui? Kami akan menyelesaikan ini dengan menambahkan nilai penghilang kueri ke URL yang kami kirim ke yang baru /todos titik akhir.

Mari kita simpan nilai penghancur cache ini dalam sebuah cookie. Nilai itu dapat diatur di server tetapi tetap dibaca di klien. Mari kita lihat beberapa contoh kode.

Kita dapat membuat sebuah +layout.server.js file di akar kami routes map. Ini akan berjalan saat startup aplikasi, dan merupakan tempat yang tepat untuk menetapkan nilai cookie awal.

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

Anda mungkin telah memperhatikan isDataRequest nilai. Ingat, tata letak akan dijalankan kembali kapan saja panggilan kode klien invalidate(), atau kapan pun kami menjalankan tindakan server (dengan asumsi kami tidak mematikan perilaku default). isDataRequest menunjukkan proses ulang tersebut, jadi kami hanya menyetel cookie jika itu false; jika tidak, kami mengirimkan apa yang sudah ada.

Grafik httpOnly: false bendera juga penting. Ini memungkinkan kode klien kami untuk membaca nilai cookie ini document.cookie. Ini biasanya menjadi masalah keamanan, tetapi dalam kasus kami ini adalah angka yang tidak berarti yang memungkinkan kami untuk melakukan cache atau cache bust.

Membaca nilai cache

Pemuat universal kami adalah apa yang disebut milik kami /todos titik akhir. Ini berjalan di server atau klien, dan kita perlu membaca nilai cache yang baru saja kita atur di mana pun kita berada. Relatif mudah jika kita berada di server: kita bisa menelepon await parent() untuk mendapatkan data dari tata letak induk. Tetapi pada klien, kita perlu menggunakan beberapa kode kasar untuk diurai 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] ?? "";
};

Untungnya, kita hanya membutuhkannya sekali.

Mengirimkan nilai cache

Tapi sekarang kita perlu mengirim nilai ini untuk kami /todos titik akhir.

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') memiliki tanda centang di dalamnya untuk melihat apakah kita berada di klien (dengan memeriksa jenis dokumen), dan tidak mengembalikan apa pun jika kita berada, pada titik mana kita tahu bahwa kita berada di server. Kemudian menggunakan nilai dari tata letak kami.

Menghancurkan cache

Tapi bagaimana apakah kita benar-benar memperbarui nilai penghancur cache itu saat kita perlu? Karena disimpan dalam cookie, kita dapat memanggilnya seperti ini dari tindakan server apa pun:

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

Pelaksanaan

Semuanya menurun dari sini; kami telah melakukan kerja keras. Kami telah membahas berbagai primitif platform web yang kami butuhkan, serta ke mana mereka pergi. Sekarang mari kita bersenang-senang dan menulis kode aplikasi untuk menyatukan semuanya.

Untuk alasan yang akan sedikit menjadi jelas, mari kita mulai dengan menambahkan fungsionalitas pengeditan ke /list halaman. Kami akan menambahkan baris tabel kedua ini untuk setiap todo:

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>

Dan, tentu saja, kita perlu menambahkan form action untuk /list halaman. Tindakan hanya bisa masuk .server halaman, jadi kami akan menambahkan +page.server.js di kami /list map. (Iya +page.server.js file dapat berdampingan di sebelah a +page.js mengajukan.)

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

Kami mengambil data formulir, memaksa penundaan, memperbarui todo kami, dan kemudian, yang paling penting, membersihkan cookie bust cache kami.

Mari kita coba ini. Muat ulang halaman Anda, lalu edit salah satu item daftar tugas. Anda akan melihat pembaruan nilai tabel setelah beberapa saat. Jika Anda melihat di tab Jaringan di DevToold, Anda akan melihat pengambilan ke /todos titik akhir, yang mengembalikan data baru Anda. Sederhana, dan berfungsi secara default.

Menyimpan data

Pembaruan langsung

Bagaimana jika kita ingin menghindari pengambilan yang terjadi setelah kita memperbarui item tugas, dan sebagai gantinya, memperbarui item yang dimodifikasi langsung di layar?

Ini bukan hanya masalah kinerja. Jika Anda menelusuri "postingan" lalu menghapus kata "postingan" dari salah satu item tugas dalam daftar, item tersebut akan hilang dari daftar setelah diedit karena tidak lagi ada di hasil penelusuran halaman tersebut. Anda dapat membuat UX lebih baik dengan beberapa animasi menarik untuk tugas akhir, tetapi katakanlah kami ingin tidak jalankan kembali fungsi pemuatan halaman itu tetapi masih kosongkan cache dan perbarui tugas yang dimodifikasi sehingga pengguna dapat melihat hasil edit. SvelteKit memungkinkan itu — mari kita lihat caranya!

Pertama, mari buat satu perubahan kecil pada loader kita. Alih-alih mengembalikan item tugas kami, mari kembalikan a toko yang dapat ditulisi berisi daftar tugas kami.

return { todos: writable(todos),
};

Sebelumnya, kami mengakses to-dos kami di data prop, yang bukan milik kami dan tidak dapat diperbarui. Tapi Svelte memungkinkan kami mengembalikan data kami di toko mereka sendiri (dengan asumsi kami menggunakan pemuat universal, yaitu kami). Kami hanya perlu membuat satu tweak lagi untuk kami /list .

Alih-alih ini:

{#each todos as t}

... kita perlu melakukan ini sejak itu todos sendiri sekarang menjadi toko.:

{#each $todos as t}

Sekarang data kita dimuat seperti sebelumnya. Tapi sejak todos adalah toko yang dapat ditulisi, kami dapat memperbaruinya.

Pertama, mari berikan fungsi ke use:enhance atribut:

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

Ini akan berjalan sebelum pengiriman. Mari kita tulis selanjutnya:

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

Fungsi ini menyediakan a data objek dengan data formulir kami. Kita kembali fungsi async yang akan berjalan setelah pengeditan kami selesai. Dokumen menjelaskan semua ini, tetapi dengan melakukan ini, kami mematikan penanganan formulir default SvelteKit yang akan menjalankan kembali loader kami. Inilah yang kami inginkan! (Kami dapat dengan mudah mendapatkan kembali perilaku default itu, seperti yang dijelaskan oleh dokumen.)

Kami sekarang menelepon update pada kami todos array karena ini adalah toko. Dan itu saja. Setelah mengedit item agenda, perubahan kami segera muncul dan cache kami dihapus (seperti sebelumnya, karena kami menetapkan nilai cookie baru di editTodo bentuk tindakan). Jadi, jika kita menelusuri lalu menavigasi kembali ke halaman ini, kita akan mendapatkan data baru dari loader kita, yang akan dengan benar mengecualikan setiap item agenda yang diperbarui yang telah diperbarui.

Kode untuk pembaruan segera tersedia di GitHub.

Menggali lebih dalam

Kami dapat mengatur cookie di fungsi pemuatan server apa pun (atau tindakan server), bukan hanya tata letak root. Jadi, jika beberapa data hanya digunakan di bawah satu tata letak, atau bahkan satu halaman, Anda dapat menyetel nilai cookie di sana. Terlebih lagi, jika Anda tidak melakukan trik yang baru saja saya tunjukkan memperbarui data di layar secara manual, dan sebagai gantinya ingin loader Anda berjalan kembali setelah mutasi, maka Anda selalu dapat menyetel nilai cookie baru tepat di fungsi muat itu tanpa pemeriksaan apa pun isDataRequest. Ini akan diatur pada awalnya, dan kemudian setiap kali Anda menjalankan tindakan server, tata letak halaman akan secara otomatis membatalkan dan memanggil kembali loader Anda, menyetel ulang string bust cache sebelum loader universal Anda dipanggil.

Menulis fungsi reload

Mari selesaikan dengan membuat satu fitur terakhir: tombol muat ulang. Mari beri pengguna tombol yang akan menghapus cache dan kemudian memuat ulang kueri saat ini.

Kami akan menambahkan tindakan bentuk sederhana:

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

Dalam proyek nyata Anda mungkin tidak akan menyalin/menempelkan kode yang sama untuk mengatur cookie yang sama dengan cara yang sama di banyak tempat, tetapi untuk posting ini kami akan mengoptimalkan kesederhanaan dan keterbacaan.

Sekarang mari buat formulir untuk mempostingnya:

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

Itu bekerja!

UI setelah memuat ulang.

Kita bisa menyebut ini selesai dan melanjutkan, tetapi mari kita tingkatkan solusi ini sedikit. Secara khusus, mari berikan umpan balik pada halaman untuk memberi tahu pengguna bahwa pemuatan ulang sedang terjadi. Juga, secara default, tindakan SvelteKit tidak valid segala sesuatu. Setiap tata letak, halaman, dll. Dalam hierarki halaman saat ini akan dimuat ulang. Mungkin ada beberapa data yang dimuat sekali di tata letak root yang tidak perlu kami batalkan atau muat ulang.

Jadi, mari kita fokus sedikit, dan hanya memuat ulang to-dos kita saat kita memanggil fungsi ini.

Pertama, mari berikan fungsi untuk meningkatkan:

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

Kami sedang menetapkan yang baru reloading variabel ke true di awal dari tindakan ini. Dan kemudian, untuk mengesampingkan perilaku default membatalkan semuanya, kami mengembalikan an async fungsi. Fungsi ini akan berjalan ketika tindakan server kami selesai (yang baru saja menetapkan cookie baru).

Tanpa ini async fungsi dikembalikan, SvelteKit akan membatalkan semuanya. Karena kami menyediakan fungsi ini, itu tidak akan membatalkan apa pun, jadi terserah kami untuk memberi tahu apa yang harus dimuat ulang. Kami melakukan ini dengan invalidate fungsi. Kami menyebutnya dengan nilai reload:todos. Fungsi ini mengembalikan janji, yang diselesaikan saat pembatalan selesai, pada titik mana kita menetapkan reloading kembali ke false.

Terakhir, kita perlu menyinkronkan loader kita dengan yang baru ini reload:todos nilai batal. Kami melakukannya di loader kami dengan depends fungsi:

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

Dan itu saja. depends dan invalidate adalah fungsi yang sangat berguna. Apa yang keren adalah itu invalidate tidak hanya mengambil nilai arbitrer yang kami berikan seperti yang kami lakukan. Kami juga dapat menyediakan URL, yang akan dilacak SvelteKit, dan membatalkan semua loader yang bergantung pada URL tersebut. Untuk itu, jika Anda bertanya-tanya apakah kami dapat melewatkan panggilan ke depends dan membatalkan kami /api/todos titik akhir sama sekali, Anda bisa, tetapi Anda harus menyediakan tepat URL, termasuk search istilah (dan nilai cache kami). Jadi, Anda bisa menggabungkan URL untuk penelusuran saat ini, atau mencocokkan dengan nama jalur, seperti ini:

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

Secara pribadi, saya menemukan solusi yang menggunakan depends lebih eksplisit dan sederhana. Tapi lihat dokumen untuk info lebih lanjut, tentu saja, dan putuskan sendiri.

Jika Anda ingin melihat tombol muat ulang beraksi, kodenya ada di dalamnya cabang repo ini.

Berpisah pikiran

Ini adalah posting yang panjang, tapi mudah-mudahan tidak berlebihan. Kami terjun ke berbagai cara untuk menyimpan data saat menggunakan SvelteKit. Sebagian besar ini hanyalah masalah penggunaan primitif platform web untuk menambahkan cache yang benar, dan nilai cookie, yang pengetahuannya akan bermanfaat bagi Anda dalam pengembangan web secara umum, lebih dari sekadar SvelteKit.

Selain itu, ini adalah sesuatu yang Anda benar-benar tidak perlu sepanjang waktu. Bisa dibilang, Anda seharusnya hanya meraih fitur-fitur canggih semacam ini saat Anda sebenarnya membutuhkan mereka. Jika datastore Anda melayani data dengan cepat dan efisien, dan Anda tidak berurusan dengan masalah penskalaan apa pun, tidak ada gunanya membengkak kode aplikasi Anda dengan kerumitan yang tidak perlu melakukan hal-hal yang kita bicarakan di sini.

Seperti biasa, tulis kode yang jelas, bersih, sederhana, dan optimalkan bila perlu. Tujuan dari posting ini adalah untuk memberi Anda alat pengoptimalan saat Anda benar-benar membutuhkannya. Saya harap Anda menikmatinya!

tempat_img

Intelijen Terbaru

tempat_img

Hubungi kami

Hai, yang di sana! Apa yang bisa saya bantu?