Las stores de svelte en la vida real

Svelte - 2 de febrero de 2023

Si buscar por ahí algún ejemplo de las stores de svelte te vas a encontrar el típico ejemplo de un contador. Pero no vas a ver ningún ejemplo del mundo real. Vamos a ver como deberías de crear un estado global en svelte.

Antes de seguir leyendo te recomiendo que te leas la documentación oficial de svelte para saber lo básico de las stores.

Lo básico sobre la store

Una store en svelte se ve así:

// store.ts

import { writable } from 'svelte/store';

export const store = writable<string>('value');

Cambiar el estado de la store

Para cambiar el estado de la store se utiliza el método .set() del estado.

store.set('new value');

Obtener el valor de la store

Para obtener el valor del estado se emplea el método get() importado de svelte/store.

import { get } from 'svelte/store';

const value = get(store);

Suscribirte a los cambios de la store

Te puedes suscribir a los cambios de la store de dos formas, utilizando el método de la store .subscribe() :

const unsubscribe = store.subscribe((value: string) => {
  console.log('value: ', value);
});

O también puedes utilizar la suscripción automática dentro de un componente:

// component.svelte

<script>
	import store from './store.ts';
</script>

...

// utilizando $ el dom siempre esta actualizado con los datos de la store
<h1>{$my_store}</h1>

Store solo de lectura

Estos tipos de estados son externamente solo de lectura y solo se puede actualizar dentro del callback, con el ejemplo lo entenderás mejor:

import { readable } from 'svelte/store';

const time = readable(null, set => {
	set(new Date());

	const interval = setInterval(() => {
		set(new Date());
	}, 1000);

	return () => clearInterval(interval);
});

Store derivada

Una store derivada, como su nombre indica se emplean para derivar de una o varias stores, como antes, con un ejemplo se ve mejor:

import { derived } from 'svelte/store';

const tick = derived(frequency, ($frequency, set) => {
	const interval = setInterval(() => {
	  set(Date.now());
	}, 1000 / $frequency);

	return () => {
		clearInterval(interval);
	};
}, 'one moment...');

El callback de esta store se llama la primera vez que alguien se suscribe y cuando algunas de las stores de las que depende cambia.

La función que está devolviendo el callback es el callback de desuscripción será llamado cuando el último suscriptor se desuscriba.

Y hasta aquí lo único que he hecho ha sido traducir la documentación que podías encontrar en la página de svelte. ¡Ahora viene lo bueno!

La store de svelte en la vida real

Todo muy bonito para hacer una aplicación pequeña, pero ¿Cómo implementamos una store que sea escalable en la vida real? Tal y como está planteado, con este manejador de estados en malas manos nos podemos encontrar auténticas barbaridades.

Dándole un par de vueltas, he encontrado la que creo que sería la mejor manera de implementar una store en svelte. La idea principal es crear una store writable que solo estará disponible internamente y exportaremos una store derivada de la store writable, para poder acceder a los datos desde el exterior.

//user-store.ts

import { derived, writable, type Readable } from "svelte/store";



// Definimos el tipo del estado
interface UserState {
  name: string
}

// Definimos el tipo los actions del estado
interface Store extends Readable<UserState> {
  changeName(name: string): void
}



// Inicializamos el userStore writable, unicamente se podra manipular internamente 
const _userStore = writable<UserState>({
  name: 'Pepe',
})



export const userStore: Store = {
  // creamos una store derivada de la _userStore, para poder acceder desde el exterior a los datos
  ...derived(_userStore, $s => $s),
  
  // definimos las acciones, que serean las encargadas de modificar el estado
  changeName(name: string) {
    _userStore.update(state => {
      state.name = name

      return state
    })
  }
}

Y así se vería desde un componente. Solo tienes acceso a las actions, al subscribe y al estado únicamente de lectura.

<script lang="ts">
  import { get } from "svelte/store";
  import { onMount } from "svelte/types/runtime/internal/lifecycle";
  import { userStore } from "../store/test-store/store";

  
  function changeName() {
    userStore.changeName('Jose')
  }
  
  userStore.subscribe((userState) => {
    console.log('la store ha cambiado', userState)
  })

  onMount(() => {
    const name = get(userStore).name
  })
</script>

<h1>Hola { $userStore.name }</h1>

<button on:change={changeName}>Cambiar nombre</button>

Mejorando las actions

Aún se podría mejorar más las actions de las stores para que sea más escalable, extrayendo las actions en otro fichero. La estructura de carpetas quedaría así:

├── stores
│   ├── user
│   │   ├── user-store.ts
│   │   ├── actions
│   │   │   ├── change-name.ts
│   ├── post
│   │   ├── post-store.ts
│   │   ├── actions
│   │   │   ├── obtain-post.ts
│   ├── ...

Concusión

¿Realmente es necesario hacerlo de esta manera? Pues la verdad es que no, en la propia documentación oficial ya nos dicen que podemos utilizar cualquier librería para gestionar los estados globales, pero personalmente creo que no es necesario. Si svelte/store fuese un módulo independiente a svelte, no vería ningún problema en usar otra librería, pero como ya viene preinstalado estaríamos cargando dos librerías que hacen lo mismo. Es decir más peso en nuestra web y código que no se usa.

Esto lo puedes adaptar a tus necesidades, espero que te sirva de ayuda o de guía para gestionar los estados globales de tu aplicación en svelte. Si consideras que se puede mejorar me encantaría escucharlo.