vuejs-gestion-estado-con-pinia

Qué es y cómo el gestor de estado Pinia en Vue.js

  • 5 min

Pinia es el sistema de gestión de estado oficial para Vue.js, diseñado para ser intuitivo, flexible y compatible con TypeScript

Pinia fue creada por Eduardo San Martin Morote, un miembro del equipo central de Vue.js, y se convirtió en la solución oficial de gestión de estado para Vue 3 en noviembre de 2021.

Anteriormente se usaba Vuex como librería de gestión de estado. Pero la sintaxis era más complicada.

Diseñada específicamente las nuevas funcionalidades de Vue 3, Pinia aprovecha todas las características que ofrece esta versión del framework, con mejor soporte para TypeScript y una integración más fluida con la Composition API.

Instalación y configuración de Pinia

Para comenzar, necesitamos instalar Pinia en nuestro proyecto. Si aún no lo tienes agregado, puedes hacerlo haciendo,

npm install pinia

Una vez instalado, debemos configurar Pinia en nuestra aplicación Vue. En el archivo principal (main.js o main.ts), importa y usa Pinia:

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.mount('#app');

Creación de un Store en Pinia

En Pinia, un store es un contenedor reactivo que guarda el estado y la lógica de negocio.

Vamos a ver cómo crear un store básico con un ejemplo sencillo que gestionaría el estado de usuarios.

import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    users: [],
    currentUser: null,
  }),
  actions: {
    async fetchUsers() {
      const response = await fetch('/api/users');
      this.users = await response.json();
    },
    setCurrentUser(user) {
      this.currentUser = user;
    },
  },
  getters: {
    activeUsers: (state) => state.users.filter(user => user.isActive),
  },
});

En este ejemplo tenemos,

  • Estado: users es un array vacío y currentUser es null.
  • Acciones: fetchUsers obtiene los usuarios de una API y setCurrentUser establece el usuario actual.
  • Getters: activeUsers filtra los usuarios activos.

Partes de una store

Acabamos de ver como la store está compuesta por ciertas partes o “apartados” (opcionales todas). Estas partes son,

PropiedadDescripción
stateDefine el estado inicial reactivo del store
actionsContiene métodos para modificar el estado y manejar lógica de negocio.
gettersFunciones computadas que derivan valores del estado

Vamos a ver cada una de ellas en detalle,

Estado

El estado contiene las variables y datos que gestiona la store. El estado en Pinia es automáticamente reactivo, lo que significa que cualquier cambio en el estado se reflejará inmediatamente en los componentes que lo usan.

userStore.setCurrentUser({ id: 1, name: 'John Doe' });

Por ejemplo, si actualizamos currentUser en el store, cualquier componente que dependa de currentUser se actualizará automáticamente:

Actions

Las actions manejan lógica compleja o llamadas API.

actions: {
  async fetchUsers() {
    try {
      const response = await fetch('/api/users');
      this.users = await response.json();
    } catch (error) {
      console.error('Error fetching users:', error);
    }
  },
},

Getters

Los getters son como computed() pero a nivel de store.

// stores/counter.js
getters: {
  multiplyBy: (state) => (factor) => state.count * factor,
}

Al igual que las computed properties, los métodos getters también se cachean.

Uso del Store en Componentes

Para usar el store en un componente, simplemente importamos y llamamos al store:

<script setup>
import { useUserStore } from '@/stores/user';

const userStore = useUserStore();

// Acceder al estado
console.log(userStore.users);

// Llamar a una acción
userStore.fetchUsers();

// Usar un getter
console.log(userStore.activeUsers);
</script>

Modificar el estado

Lógicamente, vamos a querer modificar el estado de la store. En Pinia tenemos varias formas de hacerlo.

La forma recomendada de modificar el estado es mediante acciones:

const counter = useCounterStore()
counter.increment()

En Pinia podemos modificar el estado directamente (sin necesidad de métodos o mutaciones).

counterStore.count++; // ✅ Funciona (reactividad mantenida)

También podemos resetear el estado de una variable de estado,

counterStore.$reset(); // Vuelve al estado inicial

Para actualizar múltiples propiedades a la vez, puedes usar $patch:

// Pasando un objeto
counter.$patch({
  count: counter.count + 1,
  name: 'Nuevo nombre'
})

// Pasando una función (más eficiente para cambios en arrays)
counter.$patch((state) => {
  state.count++
  state.items.push({ id: state.nextId++, text: 'Nuevo item' })
})

Reactividad al estado

Ahora vamos a querer que nuestra App reaccione a los cambios en la store. Es decir, vamos a querer mantener la reactividad en nuestros componentes.

Pinia proporciona un helper llamado storeToRefs que convierte automáticamente todas las propiedades del state y los getters en refs reactivas.

<script setup>
import { useUserStore } from '@/stores/user';
import { storeToRefs } from 'pinia';

const userStore = useUserStore();

const { users, currentUser, activeUsers } = storeToRefs(userStore);

// Las actions se usan directamente desde el store
userStore.fetchUsers();
</script>
  • users, currentUser, y activeUsers ahora son ref() reactivos.
  • fetchUsers y demás acciones se mantienen fuera del storeToRefs porque son funciones y no necesitan ser reactivas.

Desestructurar directamente la store haría que se perdiera la reactividad. Es decir, si haces esto,

const { users, currentUser } = userStore; // ⚠️ Esto rompe la reactividad

Vas a romper la reactividad. Por eso hemos usado storeToRefs,

Suscripciones a cambios de estado

Pinia también permite suscribirse a cambios en el estado del store. Bajo el capó, es el equivalente a usar un watch.

const unsubscribe = counter.$subscribe((mutation, state) => {
  // Se ejecuta cuando el estado cambia
  console.log('Estado actualizado:', state)
  console.log('Mutación:', mutation)
  
  // Opcionalmente, puedes guardar el estado en localStorage
  localStorage.setItem('counter', JSON.stringify(state))
})

// Más tarde puedes cancelar la suscripción
unsubscribe()

Uso de múltiples stores

En aplicaciones grandes, es común dividir el estado en múltiples stores. Pinia facilita esta organización al permitir que cada store sea independiente.

Por ejemplo, podríamos tener un store para usuarios, otro para productos y otro para la configuración de la aplicación.

// stores/
export const useUserStore = defineStore('user', {
  state: () => ({
    users: [],
  }),
});
export const useProductStore = defineStore('product', {
  state: () => ({
    products: [],
  }),
});