tutoriales.com

Svelte y Fetching de Datos: Estrategias para una Experiencia Reactiva

Este tutorial explora a fondo las diversas estrategias para el fetching de datos en aplicaciones Svelte, desde la carga inicial hasta técnicas de optimización. Aprenderás a integrar datos de APIs externas de manera eficiente, manteniendo la reactividad y mejorando la experiencia del usuario. Prepárate para construir aplicaciones Svelte más dinámicas y performantes.

Intermedio15 min de lectura5 views19 de marzo de 2026Reportar error

Svelte se ha ganado un lugar en el corazón de los desarrolladores web por su enfoque en la compilación y la reactividad intrínseca. Sin embargo, como en cualquier framework, el manejo de la carga de datos es un aspecto crucial para construir aplicaciones eficientes y con una excelente experiencia de usuario. En este tutorial, profundizaremos en cómo Svelte aborda el fetching de datos, explorando distintas estrategias, desde las más básicas hasta las más avanzadas.

🚀 Introducción al Fetching de Datos en Svelte

El fetching de datos es el proceso de obtener información de una fuente externa, generalmente una API, para mostrarla en tu aplicación. En el contexto de Svelte, donde la reactividad es clave, es fundamental integrar este proceso de forma que los componentes se actualicen automáticamente cuando los datos estén disponibles o cambien.

Svelte no impone una forma única de hacer fetching de datos, lo que te da flexibilidad para elegir la estrategia que mejor se adapte a tus necesidades. Las opciones van desde usar la API nativa fetch dentro de un componente hasta soluciones más sofisticadas que manejan estados de carga y errores de forma global.

🔥 Importante: Aunque Svelte no tiene un ciclo de vida `mounted` explícito como otros frameworks, el `onMount` es el lugar ideal para iniciar llamadas a APIs si quieres que la lógica se ejecute solo en el cliente una vez que el componente se ha renderizado inicialmente.

🎯 Estrategias Básicas de Fetching con onMount

La forma más directa y común de cargar datos en un componente Svelte es utilizando la función onMount. Esta función de ciclo de vida se ejecuta solo una vez que el componente ha sido montado en el DOM, lo que la hace perfecta para operaciones que requieren acceso al navegador o para iniciar llamadas asíncronas.

💡 Usando fetch con onMount

Aquí tienes un ejemplo básico de cómo cargar datos de una API usando fetch y onMount:

<script>
  import { onMount } from 'svelte';

  let data = null;
  let loading = true;
  let error = null;

  onMount(async () => {
    try {
      const response = await fetch('https://api.example.com/items');
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      data = await response.json();
    } catch (e) {
      error = e.message;
    } finally {
      loading = false;
    }
  });
</script>

{#if loading}
  <p>Cargando datos...</p>
{:else if error}
  <p class="error">Error: {error}</p>
{:else}
  <ul>
    {#each data as item}
      <li>{item.name}</li>
    {/each}
  </ul>
{/if}

<style>
  .error { color: red; }
</style>

En este ejemplo:

  • data, loading y error son variables reactivas que Svelte monitorea.
  • onMount envuelve la lógica asíncrona para cargar los datos.
  • Un bloque {#if} maneja los estados de carga, error y datos disponibles.

Manejo de Errores y Estados de Carga

Es crucial proporcionar retroalimentación al usuario mientras se cargan los datos o si ocurre un error. Las variables loading y error nos permiten hacer esto de forma reactiva.

💡 Consejo: Considera añadir un *skeleton loader* o un spinner más elaborado en lugar de solo texto para una mejor experiencia de usuario.

✨ Fetching de Datos con Svelte Stores

Para aplicaciones más grandes, manejar el fetching de datos directamente en cada componente puede llevar a la duplicación de código y dificultar la gestión del estado. Aquí es donde los Svelte Stores brillan. Puedes encapsular la lógica de fetching, el estado de carga y los errores en un store, haciendo que los datos sean accesibles de forma global y reactiva.

Creando un readable Store para Datos Asíncronos

Un readable store es ideal cuando quieres una fuente de datos que puede ser leída pero no escrita directamente por los componentes.

Vamos a crear un archivo src/stores/itemsStore.js:

// src/stores/itemsStore.js
import { readable } from 'svelte/store';

async function fetchItems() {
  try {
    const response = await fetch('https://api.example.com/items');
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.json();
  } catch (e) {
    console.error('Error fetching items:', e);
    return { error: e.message }; // Devolvemos un objeto con error
  }
}

export const items = readable({ data: [], loading: true, error: null }, (set) => {
  fetchItems().then(result => {
    if (result.error) {
      set({ data: [], loading: false, error: result.error });
    } else {
      set({ data: result, loading: false, error: null });
    }
  });

  // No es necesario retornar un unsubscribe para un readable store que carga una vez
  // A menos que uses setInterval o websockets
});

Consumiendo el Store en un Componente

Ahora, cualquier componente puede suscribirse a este store:

<script>
  import { items } from '../stores/itemsStore';

  // '$items' es la forma abreviada de Svelte para suscribirse a un store
  // y obtener su valor actual. Es reactivo.
</script>

{#if $items.loading}
  <p>Cargando datos desde el store...</p>
{:else if $items.error}
  <p class="error">Error desde el store: {$items.error}</p>
{:else}
  <ul>
    {#each $items.data as item}
      <li>{item.name}</li>
    {/each}
  </ul>
{/if}

<style>
  .error { color: red; }
</style>

Esta estrategia centraliza la lógica de fetching, lo que es ideal para datos que necesitan ser compartidos en múltiples componentes y que no cambian con frecuencia después de la carga inicial.

Usando writable Stores para Datos Mutables o Refetching

Si necesitas refetching de datos (ej., al actualizar una tabla, aplicar filtros) o quieres que el store sea mutable, un writable store es más adecuado.

src/stores/dynamicItemsStore.js:

import { writable } from 'svelte/store';

function createDynamicItemsStore() {
  const { subscribe, set, update } = writable({ data: [], loading: false, error: null });

  return {
    subscribe,
    fetchData: async (url) => {
      set({ data: [], loading: true, error: null }); // Resetear estado al iniciar la carga
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const result = await response.json();
        set({ data: result, loading: false, error: null });
      } catch (e) {
        console.error('Error fetching dynamic items:', e);
        set({ data: [], loading: false, error: e.message });
      }
    },
    reset: () => set({ data: [], loading: false, error: null })
  };
}

export const dynamicItems = createDynamicItemsStore();

Componente que lo usa:

<script>
  import { onMount } from 'svelte';
  import { dynamicItems } from '../stores/dynamicItemsStore';

  // Suscribirse al store
  const items = dynamicItems;

  onMount(() => {
    // Cargar datos cuando el componente se monte
    items.fetchData('https://api.example.com/dynamic-items');
  });

  function refreshData() {
    items.fetchData('https://api.example.com/dynamic-items');
  }
</script>

{#if $items.loading}
  <p>Cargando datos dinámicos...</p>
{:else if $items.error}
  <p class="error">Error: {$items.error}</p>
{:else}
  <button on:click={refreshData}>Recargar Datos</button>
  <ul>
    {#each $items.data as item}
      <li>{item.name} (ID: {item.id})</li>
    {/each}
  </ul>
{/if}

<style>
  .error { color: red; }
  button { margin-bottom: 1rem; padding: 0.5rem 1rem; }
</style>

Esta es una estrategia muy potente para encapsular la lógica de fetching y hacerla reutilizable y manejable. Es ideal para datos que pueden necesitar ser refrescados o que tienen lógica de actualización más compleja.


🔄 Lazy Loading y Optimización del Fetching

La carga inicial de datos es importante, pero en aplicaciones grandes, puede que no necesites todos los datos de inmediato. El lazy loading o carga diferida, junto con otras técnicas de optimización, puede mejorar significativamente el rendimiento y la percepción del usuario.

Carga Condicional de Componentes con Datos

Svelte ya optimiza la carga de componentes, pero puedes retrasar la importación y renderización de un componente que requiere datos pesados hasta que sea realmente necesario. Esto se logra con importaciones dinámicas y bloques {#await}.

<script>
  import { onMount } from 'svelte';

  let showDetails = false;
  let DetailsComponent = null;

  async function loadDetails() {
    showDetails = true;
    // Importación dinámica del componente
    const module = await import('./DetailsComponent.svelte');
    DetailsComponent = module.default;
  }
</script>

<h2>Mi Aplicación Svelte</h2>

<button on:click={loadDetails} disabled={showDetails}>
  {#if showDetails}Cargando...{:else}Ver Detalles Pesados{/if}
</button>

{#if showDetails}
  {#if DetailsComponent}
    <svelte:component this={DetailsComponent} />
  {:else}
    <p>Cargando componente de detalles...</p>
  {/if}
{/if}

<style>
  button { margin-top: 1rem; padding: 0.75rem 1.5rem; }
</style>

En este ejemplo, DetailsComponent.svelte (que podría contener su propia lógica de fetching de datos) solo se carga y renderiza cuando el usuario hace clic en el botón.

Paginación y Carga Infinita (Infinite Scroll)

Para listas largas de datos, la paginación y la carga infinita son técnicas esenciales. En Svelte, puedes implementar esto actualizando los parámetros de tu función de fetching y el estado del store.

Paginación simple:

<script>
  import { writable } from 'svelte/store';
  import { onMount } from 'svelte';

  const createPaginatedStore = () => {
    const { subscribe, set, update } = writable({ data: [], loading: false, error: null, page: 1, totalPages: 1 });

    const fetchData = async (pageNumber) => {
      update(s => ({ ...s, loading: true, page: pageNumber }));
      try {
        const response = await fetch(`https://api.example.com/items?page=${pageNumber}&limit=10`);
        if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
        const result = await response.json(); // Asumiendo que la API devuelve { items: [], totalPages: N }
        set({ data: result.items, loading: false, error: null, page: pageNumber, totalPages: result.totalPages });
      } catch (e) {
        update(s => ({ ...s, loading: false, error: e.message }));
      }
    };

    return { subscribe, fetchData, nextPage: () => update(s => { 
      const newPage = Math.min(s.page + 1, s.totalPages);
      fetchData(newPage);
      return { ...s, page: newPage };
    }), prevPage: () => update(s => {
      const newPage = Math.max(s.page - 1, 1);
      fetchData(newPage);
      return { ...s, page: newPage };
    }) };
  };

  const paginatedItems = createPaginatedStore();

  onMount(() => {
    paginatedItems.fetchData($paginatedItems.page);
  });
</script>

<h3>Lista Paginada</h3>

{#if $paginatedItems.loading}
  <p>Cargando página {$paginatedItems.page}...</p>
{:else if $paginatedItems.error}
  <p class="error">Error: {$paginatedItems.error}</p>
{:else}
  <ul>
    {#each $paginatedItems.data as item}
      <li>{item.name}</li>
    {/each}
  </ul>
  <div class="pagination-controls">
    <button on:click={paginatedItems.prevPage} disabled={$paginatedItems.page === 1}>Anterior</button>
    <span>Página {$paginatedItems.page} de {$paginatedItems.totalPages}</span>
    <button on:click={paginatedItems.nextPage} disabled={$paginatedItems.page === $paginatedItems.totalPages}>Siguiente</button>
  </div>
{/if}

<style>
  .pagination-controls { margin-top: 1rem; display: flex; justify-content: center; align-items: center; gap: 1rem; }
  button:disabled { opacity: 0.5; cursor: not-allowed; }
</style>

Carga infinita (ejemplo conceptual):

Para la carga infinita, combinarías la lógica del store con un observador de intersección (Intersection Observer API) en tu componente para detectar cuándo el usuario se acerca al final de la lista y, en ese momento, activarías una nueva llamada a fetchData que añada los nuevos ítems al array data existente en el store, en lugar de reemplazarlo.

Inicio Usuario scroll down ¿Último elemento visible? No Esperar ¿Más datos disponibles? No Fin Cargar más datos Añadir datos al estado Fin

Almacenamiento en Caché (Caching) Básico

Para evitar llamadas repetitivas a la API para los mismos datos, puedes implementar un caché simple. Esto puede ser tan sencillo como almacenar los datos en una variable global o en un store, o usar una solución más robusta como localStorage o una librería de caching.

// src/stores/cachedItemsStore.js
import { readable } from 'svelte/store';

const cache = new Map(); // Mapa para almacenar datos en caché

async function fetchAndCacheItems(cacheKey, url) {
  if (cache.has(cacheKey)) {
    console.log(`Cargando ${cacheKey} desde caché.`);
    return cache.get(cacheKey);
  }

  console.log(`Cargando ${cacheKey} desde la red.`);
  try {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    const data = await response.json();
    cache.set(cacheKey, data); // Almacenar en caché
    return data;
  } catch (e) {
    console.error('Error fetching cached items:', e);
    return { error: e.message };
  }
}

export function createCachedStore(cacheKey, url) {
  return readable({ data: [], loading: true, error: null }, (set) => {
    fetchAndCacheItems(cacheKey, url).then(result => {
      if (result.error) {
        set({ data: [], loading: false, error: result.error });
      } else {
        set({ data: result, loading: false, error: null });
      }
    });
  });
}

Uso en un componente:

<script>
  import { createCachedStore } from '../stores/cachedItemsStore';

  const cachedItems = createCachedStore('myItems', 'https://api.example.com/items');
</script>

<h3>Datos con Caché</h3>

{#if $cachedItems.loading}
  <p>Cargando datos (con caché)...</p>
{:else if $cachedItems.error}
  <p class="error">Error: {$cachedItems.error}</p>
{:else}
  <ul>
    {#each $cachedItems.data as item}
      <li>{item.name}</li>
    {/each}
  </ul>
{/if}

Este ejemplo proporciona un createCachedStore genérico que puedes usar para diferentes tipos de datos, pasándole una clave única para el caché y la URL de la API.


🔧 Herramientas Avanzadas y Consideraciones

Para proyectos más grandes o con requisitos de caching y revalidación de datos más complejos, podrías considerar librerías como Svelte Query (una adaptación de React Query para Svelte) o TanStack Query. Estas librerías abstraen gran parte de la complejidad del fetching, caching, sincronización y revalidación de datos, permitiéndote enfocarte en la interfaz de usuario.

📌 Nota: Librerías como Svelte Query ofrecen características como revalidación en foco, retries automáticos, y deduplicación de peticiones, lo cual es invaluable en aplicaciones a gran escala.

Server-Side Rendering (SSR) y Static Site Generation (SSG) con SvelteKit

Si estás usando SvelteKit (el framework de Svelte para construir aplicaciones robustas), el fetching de datos se vuelve aún más potente. SvelteKit te permite cargar datos tanto en el servidor como en el cliente, optimizando el rendimiento y el SEO.

En SvelteKit, las funciones load son el lugar principal para el fetching de datos. Estas funciones se ejecutan antes de que el componente se renderice, ya sea en el servidor (para SSR/SSG) o en el cliente (para hidratación o navegación posterior).

// src/routes/items/+page.js (Ejemplo de SvelteKit load function)

/** @type {import('./$types').PageLoad} */
export async function load({ fetch }) {
  const response = await fetch('https://api.example.com/items');
  const items = await response.json();

  // Devuelve los datos que el componente +page.svelte necesitará
  return {
    items
  };
}
<!-- src/routes/items/+page.svelte -->
<script>
  /** @type {import('./$types').PageData} */
  export let data;
</script>

<h1>Nuestros Items</h1>

{#if data.items}
  <ul>
    {#each data.items as item}
      <li>{item.name}</li>
    {/each}
  </ul>
{:else}
  <p>No se encontraron items.</p>
{/if}

Este enfoque de SvelteKit es el más recomendado para aplicaciones que buscan SEO, rendimiento y una experiencia de usuario fluida, ya que los datos se precargan y se envían con el HTML inicial.

Ventajas de usar SvelteKit para el Fetching
  • **Mejor SEO:** El contenido está disponible en el HTML inicial para los rastreadores de motores de búsqueda.
  • **Rendimiento mejorado:** Los usuarios ven contenido más rápido, ya que los datos se obtienen antes de que el JavaScript del cliente se ejecute.
  • **Caché inteligente:** SvelteKit maneja automáticamente el caching de las respuestas de las funciones `load`.
  • **Manejo de errores:** Puedes redirigir o mostrar páginas de error directamente desde la función `load`.

✅ Conclusión

Dominar el fetching de datos en Svelte es esencial para construir aplicaciones web eficientes, reactivas y con una excelente experiencia de usuario. Desde el uso básico de onMount hasta la centralización con Svelte Stores y las potentes capacidades de SvelteKit, tienes un abanico de opciones para elegir.

La clave está en seleccionar la estrategia adecuada para cada escenario, considerando factores como la necesidad de compartir datos, la frecuencia de actualización, el rendimiento y los requisitos de SEO.

¡Tutorial Completado!

Experimenta con estas técnicas y verás cómo tus aplicaciones Svelte se vuelven más robustas y agradables tanto para los desarrolladores como para los usuarios finales.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!