Introducción a Pinia y Vue 3

Da los primeros pasos con Pinia y Vue 3 utilizando la Composition API. Pinia permite compartir estado sin la complejidad de Vuex. Es una librería ligera, extensible y modular.


Si has trabajado con Vue 2 seguro que habrás usado Vuex: la solución oficial de Vue para el manejo de estado.

Ahora, con Vue 3 y su Composition API, no tenemos nueva versión de Vuex sino su reemplazo: Pinia. En este DevTip aprenderás a dar los primeros pasos con Pinia.

¿Pero, qué es Pinia exactamente?.

¿Qué es Pinia?

Pinia elimina gran parte de la complejidad de Vuex en varias áreas:

  • Ya no existen módulos como tal, sino diferentes stores (como las hojas de una piña 🍍) que se pueden comunicar facilmente.
  • No existen mutaciones porque no son necesarias para llevar el control de los cambios de estado (con la API de reactividad de Vue 3 expuesta, podemos mutar state desde cualquier lugar).
  • Es mucho más sencillo importar nuestras stores en cualquier componente o composable.
  • Mejor soporte (por defecto) para TypeScript.
  • Soporte nativo para Vue DevTools 🔥.
  • Extremadamente ligera: alrededor de 1.5kb.

Te sugiero que le eches un vistazo a la documentación oficial de Pinia.

Usando Vue 3 y Pinia

Instalación de Pinia

Lo primero, por supuesto, es instalar Pinia y añadirla a nuestro setup. Así que yarn add pinia y luego:

JS
// main.js
import {createApp} from 'vue'
import {createPinia} from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')

Crear una store con Pinia

Ahora podemos crear nuestra store, por ejemplo, en stores/counter.js. Aquí haremos uso del método de Pinia defineStore para crear una nueva store con al menos la propiedad options para el estado, una acción increment para mutarla y un getter totalClicks que nos devolverá un valor computado.

JS
// stores/counter.js
import {defineStore} from 'pinia';
export const useCounterStore = defineStore('counter', {
    state: () => ({
        options: {
            a: 0,
            b: 0,
            c: 0,
        },
    }),
    actions: {
        increment(option) {
            this.options[option]++;
        },
    },
    getters: {
        totalClicks() {
            return Object.values(this.options).reduce((total, current) => {
                return total + current;
            }, 0);
        },
    },
});

Utilizando una store creada con Pinia

Para usar una store creada con Pinia solo tenemos que importarla en cualquier componente o composable Vue 3, así que vamos a importar useCounterStore para luego ejecutar la función que nos devolverá la store.

Guardamos todo ello en la constante store.

<!-- Component.vue -->
<script setup>
import { useCounterStore } from '../stores/counter';
const store = useCounterStore();
</script>

Ahora podemos hacer uso de sus propiedades: estado, acciones y getters. Al usar la sintaxis script setup tenemos acceso directo a todo ello en el template.

Fíjate como interpolamos el getter totalCkicks (reactivo, por supuesto), mostramos el valor de cada propiedad de options con store.options[option] y llamamos a la acción store.increment[option] como si fuera un método más ( lo es).

<!-- Component.vue -->
<template>
  <h2>Total Clics: {{ store.totalClicks }}</h2>
  <div class="card">
    <button type="button" @click="store.increment('a')">
      Option A clicked {{ store.options['a'] }} times
    </button>
    <button type="button" @click="store.delayedIncrement('b')">
      Option B clicked {{ store.options['b'] }} times
    </button>
    <button type="button" @click="store.increment('c')">
      Option C clicked {{ store.options['c'] }} times
    </button>
  </div>
</template>

Acciones asíncronas con Pinia

Igual que con Vuex, las acciones de Pinia también puede ser asíncronas. Imagina un caso en la que realizas una petición HTTP y tienes que esperar a obtener la respuesta para actualizar el estado o llamar a otras acciones.

Vamos a crear delayedIncrement para que espere una cantidad de milisegundos antes de actualizar options.

Tan sencillo como esto:

JS
// stores/counter.js
const wait = (time = 1000) =>
  new Promise((resolve) => setTimeout(resolve, time));
export const useCounterStore = defineStore('counter', {
  ...
  actions: {
    ...
        async delayedIncrement(option) {
        await wait();
        this.options[option]++;
    },
  },
    ...
});

Desestructurar Pinia con storeToRefs

Igual que con la API reactive de Vue 3, no podemos desestructurar los valores resultantes al instanciar nuestra store. Esto no funcionará:

JS
const store = useCounterStore();
const { options, increment, totalClicks } = useCounterStore(); // ❌

Para evitar perder la reactividad en tu store usa el helper de Pinia storeToRefs:

JS
import {storeToRefs} from 'pinia'
const store = useCounterStore();
const {options, increment, totalClicks} = storeToRefs(store); // ✅

Conclusión

  • Ya ves lo sencillo que es dar los primeros pasos con Pinia y Vue 3.
  • Happy coding 🔥

Demo

Juan Andrés Núñez
Juan Andrés Núñez
Ingeniero Frontend. Especialista en Vue.js. Docente profesional. Estoico.