Svelte siempre fue el framework que te hacía sentir que escribir UI era fácil. El compilador hacía magia: declarabas una variable y era reactiva sin que tú hicieras nada. El problema es que la magia, cuando falla, es imposible de debuguear.
Svelte 5 introduce las Runas: funciones explícitas que declaran reactividad. Adiós magia, adiós sorpresas.
$state: la reactividad explícita
En Svelte 4:
<script>
let count = 0; // mágicamente reactivo
</script>
En Svelte 5:
<script>
import { useState } from 'svelte';
let count = $state(0);
</script>
<button onclick={() => count++}>
{count}
</button>
La diferencia: $state() es una señal (signal). Sabes exactamente qué
es reactivo y qué no. El compilador no tiene que adivinar.
$derived: valores calculados
<script>
let items = $state([]);
let total = $derived(items.reduce((sum, i) => sum + i.price, 0));
</script>
<p>Total: {total}€</p>
Cada vez que items cambia, total se recalcula. Sin suscripciones
manuales, sin useMemo, sin nada. Es una derivación pura.
$effect: efectos con cleanup
<script>
let query = $state('');
$effect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(r => r.json())
.then(data => results = data);
return () => controller.abort();
});
</script>
La función que devuelve $effect es el cleanup. Se ejecuta antes de
cada re-ejecución y al desmontar el componente. Nada de onDestroy
separado.
Reactividad profunda
$state es profundo por defecto. Si tienes un objeto anidado y
modificas una propiedad, el cambio se propaga:
<script>
let form = $state({
name: '',
address: {
city: '',
zip: ''
}
});
</script>
<!--Modificar form.address.city dispara re-render -->
<input bind:value={form.address.city} />
No necesitas spread operators ni trucos. Svelte 5 trackea las
mutaciones dentro de objetos $state de forma granular.
Snippets: la nueva forma de reutilizar UI
Svelte 5 introduce snippets — bloques de markup reutilizables dentro de un componente:
{#snippet card(title, content)}
<div class="card">
<h3>{title}</h3>
<p>{content}</p>
</div>
{/snippet}
{@render card('Hola', 'Este es el contenido')}
{@render card('Otro', 'Más contenido')}
Los snippets son más ligeros que los componentes: no crean un nuevo scope, no tienen lifecycle. Son trozos de UI que puedes invocar como funciones.
Mi veredicto
Las Runas hacen que Svelte 5 sea más predecible. Ya no hay reglas ocultas sobre qué variables son reactivas y cuáles no. Es explícito. Y el hecho de que la reactividad profunda funcione sin spread operators es un win enorme para formularios y estado anidado.
Lo que pierde en magia, lo gana en confianza. Y cuando debugueas a las 3 de la mañana, confianza es todo lo que quieres.