SvelteKit er det nyeste rammeverket på listen min, og å bygge frontenden til denne bloggen med det er genuint det første ekte jeg har sendt ut i det.
Før dette var det mest React: en treningssporer-app, et MERN-stack-butikkprosjekt (Mongo, Express, React, Node, hele settet). Reacts mentale modell, komponenter, props, state, re-rendere når state endres, tok meg en stund å faktisk bli komfortabel med. SvelteKit ber deg tenke på nesten ingenting av det på samme måte, som var mer desorienterende enn jeg forventet på forhånd.
den første buggen var en vane, ikke egentlig en feil
Min aller første Svelte-komponent var en liten tag-liste for admin-innleggseditoren, legg til en tag, se den dukke opp i listen. Rett frem, tenkte jeg, kommende fra React der jeg hadde gjort akkurat dette mønsteret et titalls ganger.
<script>
let tags = ["python", "learning"];
function addTag(newTag) {
tags.push(newTag);
}
</script>
<button on:click={() => addTag("sveltekit")}>add tag</button>
{#each tags as tag}
<span>{tag}</span>
{/each}Klikket på knappen. Ingenting skjedde. Ikke en feilmelding, ikke en advarsel, listen bare stille vokste ikke, som på et vis er mer forvirrende enn et krasj ville vært.
det jeg faktisk googlet
"svelte array push not updating ui" dukket opp med akkurat den saken nesten umiddelbart, og det er visstnok vanlig nok til å ha sin egen forklaring i Svelte-dokumentasjonen som jeg tydeligvis ikke hadde lest ennå: Sveltes reaktivitet utløses av tilordning, ikke av mutasjon. tags.push(newTag) muterer det eksisterende arrayet på stedet, og Sveltes kompilator vet bare å re-rendre når den ser en faktisk tilordning til tags, noe sånt som tags = tags eller et genuint nytt array. Å pushe endrer arrayets innhold uten noensinne å tilordne noe, så ingenting forteller Svelte å se på nytt.
<script>
let tags = ["python", "learning"];
function addTag(newTag) {
tags = [...tags, newTag];
}
</script>Én linje annerledes. Spre det gamle arrayet inn i et nytt, tilordne det tilbake til tags, og nå er hver tillegg en ekte tilordning Svelte kan se. I React er akkurat denne vanen håndhevet av konvensjon (setTags([...tags, newTag]) er bare hvordan useState fungerer, du kan ikke mutere deg rundt det selv om du ville), så jeg hadde aldri faktisk måttet tenke på hvorfor regelen eksisterte. Svelte lot meg mutere direkte, som føltes som mindre seremoni, helt til det ikke fungerte og jeg ikke hadde noen anelse om hvorfor.
ingen virtuell DOM å resonnere om
Svelte kompilerer komponenten din til kode som direkte oppdaterer DOM-en når en verdi endres. Det finnes ikke noe virtuelt DOM-diffing-steg å mentalt modellere, som høres ut som det burde gjøre ting enklere og mest gjør det, bortsett fra at jeg fortsatte å lete etter useState-ekvivalenten som bare ikke finnes i samme form.
<script>
let count = 0;
</script>
<button on:click={() => count += 1}>
clicked {count} times
</button>Det er hele komponenten. Å tilordne til count er reaktivitetsutløseren. I React ville jeg grepet etter useState av vane og så husket: riktig, bare tilordne variabelen på nytt, det er hele mekanismen her, og det er den samme regelen som ødela tag-listen min, bare i et annet antrekk denne gangen fordi += er en tilordning.
skjema-handlinger var den faktiske overraskelsen
Kommende fra React og Express, der hver skjemainnsending betyr å skrive et fetch-kall på klienten og en matchende API-rute på serveren for hånd, forvirret SvelteKits skjema-handlinger meg først fordi de så ut som de erstattet skjemaet i stedet for bare å håndtere innsendingen. Mitt første instinkt var å kjempe mot det, og bygge React-versjonen likevel av vane.
<!-- what I tried first, out of old habit -->
<script>
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const res = await fetch("/api/comment", {
method: "POST",
body: JSON.stringify(Object.fromEntries(formData))
});
}
</script>
<form on:submit={handleSubmit}>
<textarea name="body"></textarea>
<button type="submit">Post comment</button>
</form>Fungerte, teknisk sett. Men det betydde å håndskrive en API-rute for å motta det fetch-kallet, håndskrive JSON-parsingen, håndskrive feilhåndteringen, alt det jeg allerede hadde gjort i Express, ingenting av det SvelteKit faktisk trengte at jeg gjorde selv.
// +page.server.ts, the SvelteKit way
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const body = data.get("body");
if (!body) {
return fail(400, { error: "comment cannot be empty" });
}
// save it
return { success: true };
}
};<form method="POST">
<textarea name="body"></textarea>
<button type="submit">Post comment</button>
</form>
Jeg slo av JavaScript i devtools av nysgjerrighet, på den rene method="POST"-versjonen. Skjemaet fungerte fortsatt. Det er øyeblikket det virkelig klikket: handlingen er håndtereren, og use:enhance er noe du legger på oppå senere for at det skal føles mer som en single-page-app, ikke noe skjemaet trenger for å fungere i det hele tatt. MERN-butikken min fungerte aldri slik. Drep JS-en på den appen og det var en statisk side med døde knapper, fordi hele innsendingsstien bodde helt på klienten.
det reaktive uttrykket som stille brukte en foreldet verdi
$:-uttrykk er Sveltes andre store reaktivitetsmekanisme, for verdier beregnet fra andre verdier, og jeg ble bitt en gang av å anta at de kjører på nytt mer aggressivt enn de faktisk gjør.
<script>
let posts = [];
let searchTerm = "";
$: filtered = posts.filter((p) => p.title.includes(searchTerm));
async function loadPosts() {
const res = await fetch("/api/posts");
posts = await res.json();
}
</script>Dette ser fint ut, og er det for det meste. Buggen dukket opp da jeg refaktorerte loadPosts til å mutere et eksisterende array med posts.push(...newPosts) for en "last mer"-knapp i stedet for å tilordne posts på nytt helt og holdent, akkurat samme mutasjon-versus-tilordning-feil som tag-listen, bare i et annet antrekk denne gangen. filtered regnet aldri på nytt etter "last mer," fordi Sveltes avhengighetssporing for $: overvåker tilordning til posts, samme regel som overalt ellers, og et mutert array utløser den aldri. Så snart jeg gjenkjente formen på buggen fra tag-listen tidligere, var fiksen den samme ene linjen: tilordne på nytt i stedet for å mutere.
filbasert ruting snublet meg opp i omtrent en dag
Kommende fra Express, der hver rute er en eksplisitt kodelinje (app.get("/posts/:slug", ...)), føltes SvelteKits filbaserte ruting, der en mappe kalt [slug] i routes-katalogen bare blir den dynamiske ruten, som magi jeg ikke stolte på den første dagen. Jeg fortsatte å forvente å finne den "ekte" ruterkonfigurasjonen et sted og var forvirret over at den ikke eksisterte, at mappestrukturen genuint var hele rutetabellen. Så snart det klikket føltes det åpenbart, på samme måte som tilordningsregelen gjorde, men dagen med å ikke stole på det var ekte.
hva som fortsatt snubler meg opp
Reaktivitet gjennom $:-uttrykk versus en ren tilordning er ikke alltid åpenbart for meg ennå, nøyaktig når Svelte kjører noe på nytt versus når den ikke gjør det, spesielt når en avhengighet er begravet inne i et funksjonskall fremfor referert direkte i uttrykket. Jeg har fått det galt minst to ganger denne måneden på måter som produserte en foreldet verdi stille i stedet for en feilmelding, som er en verre feilmodus enn Reacts "glemte det i dependency-arrayet"-versjon. I det minste kjører den vanligvis bare på nytt for ofte i stedet for for sjelden, som er den mer tilgivende retningen å ta feil i.

hvor jeg faktisk lander på det
Nyeste ting på listen, og også for øyeblikket min favoritt å faktisk skrive i til daglig, mutasjonsbugger til side. Om det er rammeverket eller bare hvetebrødsdagene til alt nytt, spør meg igjen om seks måneder når jeg har truffet hva enn SvelteKits ekvivalent til Reacts rules-of-hooks-hodepine viser seg å være. Det finnes alltid en. Så langt har min bare vært den samme leksjonen to ganger, i en tag-liste og så i et reaktivt uttrykk: endre verdien, ikke bare endre hva den peker på på stedet.