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:
// 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.
// 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:
// 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á:
const store = useCounterStore();
const { options, increment, totalClicks } = useCounterStore(); // ❌
Para evitar perder la reactividad en tu store usa el helper de Pinia storeToRefs
:
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 🔥