SvelteKit es el framework más nuevo de mi lista, y construir el frontend de este blog con él es genuinamente lo primero real que he lanzado en él.
Antes de esto era sobre todo React: una app de seguimiento de fitness, un proyecto de tienda con stack MERN (Mongo, Express, React, Node, el conjunto completo). El modelo mental de React, componentes, props, estado, re-renderizados cuando el estado cambia, me llevó un tiempo sentirme de verdad cómodo con él. SvelteKit te pide pensar en casi nada de eso de la misma manera, lo cual fue más desorientador de lo que esperaba de entrada.
el primer bug fue un hábito, no exactamente un error
Mi primerísimo componente de Svelte era una pequeña lista de etiquetas para el editor de posts de admin, añadir una etiqueta, verla aparecer en la lista. Sencillo, pensé, viniendo de React donde había hecho este mismo patrón una decena de veces.
<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}Hice clic en el botón. No pasó nada. Ni un error, ni un aviso, la lista simplemente no crecía en silencio, lo cual de alguna manera es más confuso de lo que habría sido un fallo.
lo que en realidad busqué en Google
"svelte array push not updating ui" sacó el problema exacto casi de inmediato, y por lo visto es lo bastante común como para tener su propia explicación en la documentación de Svelte que claramente todavía no había leído: la reactividad de Svelte se dispara por asignación, no por mutación. tags.push(newTag) muta el array existente en el sitio, y el compilador de Svelte solo sabe volver a renderizar cuando ve una asignación real a tags, algo como tags = tags o un array genuinamente nuevo. Hacer push cambia el contenido del array sin asignar nunca nada, así que nada le dice a Svelte que vuelva a mirar.
<script>
let tags = ["python", "learning"];
function addTag(newTag) {
tags = [...tags, newTag];
}
</script>Una línea distinta. Esparcir el array viejo dentro de uno nuevo, asignarlo de vuelta a tags, y ahora cada añadido es una asignación real que Svelte puede ver. En React este mismo hábito se impone por convención (setTags([...tags, newTag]) es simplemente cómo funciona useState, no puedes mutar tu camino alrededor de eso aunque quisieras), así que nunca había tenido que pensar en realidad en por qué existía la regla. Svelte me dejaba mutar directamente, lo cual se sentía como menos ceremonia, hasta que dejó de funcionar y no tenía ni idea de por qué.
sin DOM virtual sobre el que razonar
Svelte compila tu componente en código que actualiza el DOM directamente cuando un valor cambia. No hay ningún paso de diffing de DOM virtual que modelar mentalmente, lo cual suena como si debiera simplificar las cosas y en su mayor parte lo hace, salvo que seguía buscando el equivalente de useState que simplemente no existe con esa misma forma.
<script>
let count = 0;
</script>
<button on:click={() => count += 1}>
clicked {count} times
</button>Ese es el componente entero. Asignar a count es el disparador de reactividad. En React iría a por useState por costumbre y luego recordaría: cierto, simplemente reasigna la variable, ese es todo el mecanismo aquí, y es la misma regla que rompió mi lista de etiquetas, solo que con un disfraz distinto esta vez porque += es una asignación.
las form actions fueron la sorpresa real
Viniendo de React y Express, donde cada envío de formulario significa escribir una llamada fetch en el cliente y una ruta de API a juego en el servidor a mano, las form actions de SvelteKit me confundieron al principio porque parecía que reemplazaban el formulario en lugar de simplemente manejar su envío. Mi primer instinto fue resistirme, y construir la versión de React de todos modos por costumbre.
<!-- 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>Funcionó, técnicamente. Pero significaba escribir a mano una ruta de API para recibir ese fetch, escribir a mano el análisis de JSON, escribir a mano el manejo de errores, todo lo que ya había estado haciendo en Express, nada de lo cual SvelteKit en realidad necesitaba que hiciera yo mismo.
// +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>
Apagué JavaScript en las devtools por curiosidad, en la versión sencilla method="POST". El formulario seguía funcionando. Ese es el momento en que hizo clic de verdad: la action es el manejador, y use:enhance es algo que añades encima después para que se sienta más como una app de una sola página, no algo que el formulario necesite para funcionar en absoluto. Mi tienda MERN nunca funcionó así. Mata el JS de esa app y era una página estática con botones muertos, porque toda la ruta de envío vivía enteramente en el cliente.
la sentencia reactiva que usaba en silencio un valor obsoleto
Las sentencias $: son el otro gran mecanismo de reactividad de Svelte, para valores calculados a partir de otros valores, y me mordió una vez por asumir que se vuelven a ejecutar más agresivamente de lo que en realidad lo hacen.
<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>Esto se ve bien, y en su mayor parte lo está. El bug apareció cuando refactoricé loadPosts para mutar un array existente con posts.push(...newPosts) para un botón de "cargar más" en lugar de reasignar posts directamente, exactamente el mismo error de mutación contra asignación que la lista de etiquetas, solo que con otro disfraz esta vez. filtered nunca se recalculaba después de "cargar más", porque el seguimiento de dependencias de Svelte para $: vigila la asignación a posts, la misma regla que en todas partes, y un array mutado nunca lo dispara. En cuanto reconocí la forma del bug de la lista de etiquetas de antes, el arreglo fue la misma línea: reasignar en lugar de mutar.
el enrutado basado en archivos me hizo tropezar durante un día más o menos
Viniendo de Express, donde cada ruta es una línea de código explícita (app.get("/posts/:slug", ...)), el enrutado basado en archivos de SvelteKit, donde una carpeta llamada [slug] en el directorio de rutas simplemente se convierte en la ruta dinámica, se sintió como magia en la que no confiaba durante el primer día. Seguía esperando encontrar la configuración "real" del router en algún sitio y me confundía que no existiera, que la estructura de carpetas fuera de verdad toda la tabla de enrutado. En cuanto hizo clic se sintió obvio, igual que pasó con la regla de reasignación, pero el día de no confiar en ello fue real.
lo que todavía me hace tropezar
La reactividad a través de sentencias $: frente a una simple reasignación todavía no me resulta siempre obvia, exactamente cuándo Svelte vuelve a ejecutar algo y cuándo no, especialmente cuando una dependencia está enterrada dentro de una llamada a función en lugar de referenciada directamente en la sentencia. Lo he hecho mal al menos dos veces este mes de maneras que producían un valor obsoleto en silencio en lugar de un error, que es un modo de fallo peor que la versión de React de "se me olvidó en el array de dependencias". Al menos esa suele simplemente volver a ejecutarse demasiado a menudo en lugar de con demasiada poca frecuencia, que es la dirección más indulgente en la que equivocarse.

dónde aterrizo en realidad con esto
Lo más nuevo de la lista, y también actualmente mi favorito para escribir de verdad en el día a día, bugs de mutación aparte. Si eso es el framework o solo la fase de luna de miel de cualquier cosa nueva, pregúntame otra vez en seis meses en cuanto haya topado con lo que sea que resulte ser el equivalente en SvelteKit del dolor de cabeza de las reglas de los hooks de React. Siempre hay uno. Hasta ahora el mío ha sido simplemente la misma lección dos veces, en una lista de etiquetas y luego en una sentencia reactiva: cambia el valor, no cambies solo a qué apunta en el sitio.