¡Código - Código!

Variables reactivas → estado local en Svelte

Herramienta: Variables reactivas

Framework: Svelte

Persona Encargada: Ariel GonzAgüer

Creado: 19-9-2025

Última actualización: 19-9-2025

Este artículo cubre todo lo que necesita saber sobre el manejo de estado local en Svelte: desde variables reactivas básicas hasta patrones avanzados.

¿Qué son las variables reactivas en Svelte?

En Svelte, el estado local se maneja de forma muy directa con variables reactivas. A diferencia de otros frameworks, no necesita hooks especiales - simplemente declare variables con let y Svelte se encarga de la reactividad automáticamente.

<script>
  let count = 0;
  
  function increment() {
    count = count + 1; // Svelte detecta este cambio automáticamente
  }
</script>

<div>
  <p>Contador: {count}</p>
  <button on:click={increment}>+1</button>
</div>

Contador con Svelte

0
Estado local con variables reactivas de Svelte

1. Sintaxis básica

Declaración y uso

<script>
  // Variables reactivas simples
  let count = 0;
  let name = '';
  let isVisible = true;
  
  function toggleVisibility() {
    isVisible = !isVisible;
  }
</script>

<div>
  <p>Contador: {count}</p>
  <input bind:value={name} placeholder="Escribe tu nombre" />
  {#if isVisible}
    <p>¡Hola {name}!</p>
  {/if}
  <button on:click={toggleVisibility}>
    {isVisible ? 'Ocultar' : 'Mostrar'} saludo
  </button>
</div>

Convenciones en Svelte

2. Tipos de valores de estado

Primitivos

<script>
  let count = 0;           // número
  let message = 'Hola';    // string
  let isActive = false;    // boolean
  let data = null;         // null/undefined
</script>

<div>
  <p>Contador: {count}</p>
  <p>Mensaje: {message}</p>
  <p>Activo: {isActive ? 'Sí' : 'No'}</p>
  <p>Datos: {data ? 'Cargados' : 'Sin datos'}</p>
</div>

Objetos

<script>
  let user = {
    name: '',
    email: '',
    age: 0
  };
  
  // ✅ Correcto - reasignación completa
  function updateName() {
    user = {
      ...user,
      name: 'Juan'
    };
  }
  
  // ✅ También correcto - modificación directa + reasignación
  function updateAge() {
    user.age = user.age + 1;
    user = user; // Fuerza reactividad
  }
  
  // ✅ Más elegante - usar assignment
  function incrementAge() {
    user.age += 1;
  }
</script>

<div>
  <p>Nombre: {user.name}</p>
  <p>Email: {user.email}</p>
  <p>Edad: {user.age}</p>
  <button on:click={updateName}>Cambiar nombre</button>
  <button on:click={incrementAge}>Incrementar edad</button>
</div>

Arrays

<script>
  let tasks = [];
  let newTask = '';
  
  function addTask() {
    if (newTask.trim()) {
      // ✅ Agregar al final
      tasks = [...tasks, { id: Date.now(), text: newTask }];
      newTask = '';
    }
  }
  
  function removeTask(id) {
    // ✅ Filtrar elemento
    tasks = tasks.filter(task => task.id !== id);
  }
  
  function updateTask(id, newText) {
    // ✅ Mapear y actualizar
    tasks = tasks.map(task => 
      task.id === id ? { ...task, text: newText } : task
    );
  }
</script>

<div>
  <input 
    bind:value={newTask}
    placeholder="Nueva tarea"
  />
  <button on:click={addTask}>Agregar</button>
  
  <ul>
    {#each tasks as task (task.id)}
      <li>
        {task.text}
        <button on:click={() => removeTask(task.id)}>Eliminar</button>
      </li>
    {/each}
  </ul>
</div>

3. Declaraciones reactivas

$: para cálculos derivados

En Svelte, use $: para crear valores que se recalculan automáticamente cuando sus dependencias cambian:

<script>
  let firstName = '';
  let lastName = '';
  
  // ✅ Se recalcula automáticamente cuando firstName o lastName cambian
  $: fullName = `${firstName} ${lastName}`.trim();
  
  let numbers = [1, 2, 3, 4, 5];
  let filter = 'all';
  
  // ✅ Array filtrado reactivo
  $: filteredNumbers = filter === 'even' 
    ? numbers.filter(n => n % 2 === 0)
    : filter === 'odd'
    ? numbers.filter(n => n % 2 === 1)
    : numbers;
</script>

<div>
  <input bind:value={firstName} placeholder="Nombre" />
  <input bind:value={lastName} placeholder="Apellido" />
  <p>Nombre completo: {fullName}</p>
  
  <select bind:value={filter}>
    <option value="all">Todos</option>
    <option value="even">Pares</option>
    <option value="odd">Impares</option>
  </select>
  
  <p>Números: {filteredNumbers.join(', ')}</p>
</div>

Efectos reactivos

<script>
  let count = 0;
  
  // ✅ Efecto que se ejecuta cuando count cambia
  $: {
    console.log(`El contador cambió a: ${count}`);
    if (count > 10) {
      alert('¡Has superado 10!');
    }
  }
  
  // ✅ También puedes usar funciones
  $: updateTitle(count);
  
  function updateTitle(value) {
    if (typeof document !== 'undefined') {
      document.title = `Contador: ${value}`;
    }
  }
</script>

<div>
  <p>Contador: {count}</p>
  <button on:click={() => count++}>Incrementar</button>
</div>

4. Stores para estado global

Stores escribibles

// stores.js
import { writable } from 'svelte/store';

export const count = writable(0);
export const user = writable(null);

// En el componente
<script>
  import { count } from './stores.js';
  
  // ✅ Auto-suscripción con $
  // $count se actualiza automáticamente
</script>

<div>
  <p>Contador global: {$count}</p>
  <button on:click={() => $count++}>Incrementar</button>
  <button on:click={() => count.set(0)}>Reset</button>
</div>

Stores derivados

// stores.js
import { writable, derived } from 'svelte/store';

export const numbers = writable([1, 2, 3, 4, 5]);

export const evenNumbers = derived(
  numbers,
  $numbers => $numbers.filter(n => n % 2 === 0)
);

export const sum = derived(
  numbers,
  $numbers => $numbers.reduce((a, b) => a + b, 0)
);

5. Patrones avanzados

Componentes con props reactivas

<!-- Child.svelte -->
<script>
  export let initialValue = 0;
  
  let count = initialValue;
  
  // ✅ Reaccionar a cambios en props
  $: if (initialValue !== undefined) {
    count = initialValue;
  }
</script>

<div>
  <p>Contador: {count}</p>
  <button on:click={() => count++}>+</button>
</div>

Binding bidireccional personalizado

<!-- CustomInput.svelte -->
<script>
  export let value = '';
  
  function handleInput(event) {
    value = event.target.value;
  }
</script>

<input {value} on:input={handleInput} />

<!-- Uso -->
<script>
  import CustomInput from './CustomInput.svelte';
  let text = '';
</script>

<CustomInput bind:value={text} />
<p>Texto: {text}</p>

6. Ciclo de vida y efectos

onMount y onDestroy

<script>
  import { onMount, onDestroy } from 'svelte';
  
  let count = 0;
  let interval;
  
  onMount(() => {
    console.log('Componente montado');
    interval = setInterval(() => {
      count++;
    }, 1000);
    
    // ✅ Cleanup automático cuando se desmonta
    return () => {
      clearInterval(interval);
    };
  });
  
  onDestroy(() => {
    console.log('Componente desmontado');
    if (interval) {
      clearInterval(interval);
    }
  });
</script>

<div>
  <p>Contador automático: {count}</p>
</div>

7. Optimización y rendimiento

Keyed each blocks

<script>
  let items = [
    { id: 1, name: 'Ana' },
    { id: 2, name: 'Carlos' },
    { id: 3, name: 'María' }
  ];
  
  function shuffleItems() {
    items = items.sort(() => Math.random() - 0.5);
  }
</script>

<!-- ✅ Con key para optimización -->
{#each items as item (item.id)}
  <div>
    <input bind:value={item.name} />
    <span>{item.name}</span>
  </div>
{/each}

<button on:click={shuffleItems}>Mezclar</button>

8. Errores comunes

Reactividad con arrays y objetos

<script>
  let items = [1, 2, 3];
  let user = { name: 'Ana' };
  
  // ❌ No reactivo - Svelte no detecta el cambio
  function addItemWrong() {
    items.push(4); // Mutación directa
  }
  
  // ✅ Reactivo - reasignación
  function addItemCorrect() {
    items = [...items, 4];
  }
  
  // ❌ No reactivo
  function updateUserWrong() {
    user.name = 'Carlos'; // Sin reasignación
  }
  
  // ✅ Reactivo
  function updateUserCorrect() {
    user.name = 'Carlos';
    user = user; // Fuerza reactividad
  }
  
  // ✅ Más elegante
  function updateUserBest() {
    user = { ...user, name: 'Carlos' };
  }
</script>

9. Testing

Pruebas con Svelte Testing Library

import { render, fireEvent, screen } from '@testing-library/svelte';
import Contador from './Contador.svelte';

test('incrementa el contador al hacer click', async () => {
  render(Contador);
  
  const button = screen.getByText('+1');
  const counter = screen.getByText(/contador: 0/i);
  
  await fireEvent.click(button);
  
  expect(screen.getByText(/contador: 1/i)).toBeInTheDocument();
});

test('binding funciona correctamente', async () => {
  render(FormularioNombre);
  
  const input = screen.getByPlaceholderText('Escribe tu nombre');
  
  await fireEvent.input(input, { target: { value: 'Juan' } });
  
  expect(input.value).toBe('Juan');
});

Resumen

El manejo de estado en Svelte es:

La clave está en recordar que Svelte usa reasignación para detectar cambios. Cuando modifique objetos o arrays, siempre reasigne la variable para activar la reactividad.