Reactividad
Estado
- Svelte tiene un sistema de reactividad para mantener el DOM sincronizado con el estado de la aplicación, por ejemplo, en respuesta a un evento.
- Generalmente son estados internos (los valores solo son accesibles dentro de un componente determinado) (sin mencionar el estado compartido o universal)
- Se define la variable, y la asignación de la variable se envuelve en
$state(x) - Se llamaruna y es la forma de decirle a Svelte que no es una variable común
- Las runas parecen funciones pero no lo son
<script>
let count = $state(0); // Runa; ya es reactivo
function increment() {
count += 1; // Reasignaciones; se actualiza en automático en la UI
}
</script>
<button onclick={increment}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
Estado compartido o universal
- Las runas se pueden usar dentro de los componentes
.sveltepara añadir reactividad - También se pueden usar runas fuera de los componentes, por ejemplo, para compartir un estado global
- Para poder usar runas fuera de los componentes, la extensión del archivo debe ser
<name>.svelte.ts(no se pueden usar runas en archivos normales JS/TS)
Estado global en un archivo compartido, por ejemplo shared.svelte.ts
export const counter = $state({
count: 0
});
Uso y modificación del estado
<script>
import { counter } from './shared.svelte.js';
</script>
<button onclick={() => counter.count += 1}>
clicks: {counter.count}
</button>
Se utiliza el mismo componente varias veces, pero siguen compartiendo estado
<script>
import Counter from './Counter.svelte';
</script>
<Counter />
<Counter />
<Counter />
Reasignación vs. mutación en estado exportado
- No se puede exportar una declaración
$statesi se reasigna (count = count + 1), porque los que hacenimportno tienen forma de enterarse del cambio (solo reciben el valor, no una conexión en vivo a la variable). - Si en cambio se muta un objeto/array (
counter.count += 1), todos los importadores siguen apuntando al mismo objeto/Proxy, así que sí ven el cambio.
Regla práctica: para compartir estado entre archivos, envolver el valor en un objeto y mutar sus propiedades, en vez de exportar un primitivo suelto y reasignarlo.
// Mal: primitivo exportado, reasignarlo no se propaga a los importadores
export let count = $state(0);
export function increment() {
count += 1; // reasignación
}
// Bien: objeto exportado, se muta su propiedad
export const counter = $state({ count: 0 });
export function increment() {
counter.count += 1; // mutación
}
Deep state
- En el ejemplo anterior, el estado reacciona a las reasignaciones
- El estado también puede reaccionar a las mutaciones, llamado deep state
- Mutación no es un método ni una característica especial de Svelte, es un concepto general de JavaScript: modificar el contenido de un objeto/array sin reemplazar la variable completa.
<script>
let numbers = $state([1, 2, 3, 4]);
function addNumber() {
// Change the array
//numbers[numbers.length] = numbers.length + 1;
numbers.push(numbers.length + 1)
}
</script>
<p>{numbers.join(' + ')} = ...</p>
<button onclick={addNumber}>
Add a number
</button>
let numbers = $state([1, 2, 3, 4]);
Esto crea un array reactivo. numbers no es un array "normal" de JS, es un Proxy que envuelve [1, 2, 3, 4]. Cualquier operación sobre numbers (leer, escribir, push, etc.) pasa primero por ese Proxy.
numbers[numbers.length] = numbers.length + 1;
Desglose con un ejemplo concreto:
numberses[1, 2, 3, 4], entoncesnumbers.lengthes4.- La línea se convierte en:
numbers[4] = 5; - O sea: "pon el valor
5en la posición4" (la posición justo después del último elemento). - Resultado:
numbersahora es[1, 2, 3, 4, 5].
No se reasignó numbers (no se escribió numbers = algo). Solo se modificó una casilla del array existente. Eso es una mutación. Como numbers es un Proxy, este intercepta esa escritura (numbers[4] = 5), se da cuenta de que cambió, y le dice a Svelte "actualiza la UI". Por eso el <p> se refresca solo, sin necesidad de reasignar.
La alternativa con push:
function addNumber() {
numbers.push(numbers.length + 1);
}
Hace exactamente lo mismo pero de forma más natural: numbers.push(5) agrega 5 al final. push también es un método que muta el array (no crea uno nuevo), así que el Proxy lo intercepta igual y dispara la actualización.
¿Por qué la doc muestra primero la versión rara (numbers[numbers.length] = ...) en vez de ir directo a push?
Para demostrar el punto de forma explícita: quieren probar que cualquier forma de mutación funciona, no solo los métodos "conocidos" como push. Escribir directamente en un índice del array es la mutación más "cruda" posible en JS, y si Svelte detecta eso, seguro detecta también push, splice, pop, etc. Es una forma de decir: "no es magia solo para métodos específicos, es reactividad genuina a nivel de mutación".
Derived state
- Se utiliza para derivar un estado de otro (reaccionar a otro estado)
- La expresión dentro de la declaración $derived se volverá a evaluar cada vez que el estado al que hace referencia, cambie
<script>
// Original state
let numbers = $state([1, 2, 3, 4]);
// It reacts when the other state changes
let total = $derived(numbers.reduce((t, n) => t + n, 0));
function addNumber() {
numbers.push(numbers.length + 1);
}
</script>
<p>{numbers.join(' + ')} = {total}</p>
<button onclick={addNumber}>
Add a number
</button>