Pinia es la librería oficial de gestión de estado para Vue 3. Reemplaza a Vuex. El equipo de Vue la creó porque el diseño de Vuex (mutations, API basada en strings, módulos complejos) no encajaba bien con TypeScript y la Composition API.
Por qué Pinia reemplazó a Vuex
Vuex tiene tres puntos de dolor que Pinia elimina:
1. Las mutations desaparecen. En Vuex, no puedes cambiar el estado directamente. Tienes que escribir una mutation (síncrona) y hacer commit por nombre. Pinia permite que las actions cambien el estado directamente, porque el sistema de reactividad de Vue 3 rastrea los cambios automáticamente a través de DevTools sin necesitar una capa de mutations separada.
2. Inferencia completa de TypeScript. Vuex depende de claves string (commit('SET_USER'), dispatch('fetchUser'), getters.userName). Un typo en cualquiera de esos strings es un bug en runtime, no un error de compilación. Los stores de Pinia son objetos planos; tu IDE conoce cada propiedad y método.
3. Sin módulos ni namespacing. Vuex tiene un store global, dividido en módulos con namespace (store.commit('cart/ADD_ITEM')). Los stores de Pinia son independientes, cada uno es su propio defineStore(), importado directamente donde se necesite.
Comparación lado a lado
// Vuex: mutations requeridas, basado en strings
store.commit('SET_COUNT', 5)
store.dispatch('fetchUsers')
store.getters.activeUsers
// Pinia: directo, type-safe
counterStore.count = 5
await userStore.fetchUsers()
userStore.activeUsersDefiniendo un store de Pinia
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const total = computed(() =>
items.value.reduce((sum, item) => sum + item.price, 0)
)
function addItem(item: CartItem) {
items.value.push(item)
}
async function checkout() {
await api.checkout(items.value)
items.value = []
}
return { items, total, addItem, checkout }
})Esto usa la sintaxis de Composition API (función setup). También hay una sintaxis Options con propiedades state, getters y actions. Mismo resultado, diferente estilo.
Usándolo en un componente
<script setup lang="ts">
import { useCartStore } from '@/stores/cart'
const cart = useCartStore()
</script>
<template>
<span>{{ cart.items.length }} items, {{ cart.total }}€</span>
<button @click="cart.checkout()">Checkout</button>
</template>Sin commit, sin dispatch, sin mapGetters. Solo importa el store y úsalo.
Tabla de diferencias clave
| Aspecto | Vuex | Pinia |
|---|---|---|
| Mutations | Requeridas (síncronas) | No existen |
| TypeScript | Limitado, API basada en strings | Inferencia completa, type-safe |
| Estructura del store | Un store, módulos con namespace | Stores independientes |
| DevTools | Sí | Sí (time-travel, edición de estado) |
| SSR | Requiere configuración cuidadosa | Soporte integrado |
| Tamaño del bundle | ~6kb | ~1kb |
Ver también: ¿Cómo funciona Vuex? · ¿Cómo funciona Pinia internamente? · ¿Cómo migrarías un módulo de Vuex a Pinia?
Referencias
- What is Pinia? - Pinia docs
- Comparison with Vuex - Pinia docs
- Migrating from Vuex - Pinia docs