Vue 3 Pinia vs Vuex: Gestión de Estado Moderna y Preguntas de Entrevista 2026

Comparación detallada entre Pinia y Vuex: diseño de API, soporte TypeScript, Composition API, rendimiento, estrategias de migración y preguntas frecuentes en entrevistas Vue para 2026.

Diagrama comparativo de gestión de estado Vue 3 Pinia vs Vuex

La gestión de estado en aplicaciones Vue 3 atraviesa un momento decisivo. Pinia 3 se consolidó como la biblioteca oficial recomendada por el equipo de Vue, mientras que Vuex permanece en modo mantenimiento sin planes para una versión 5. Para los desarrolladores que trabajan con Vue en 2026, dominar las diferencias entre ambas bibliotecas no solo resulta relevante para la arquitectura de proyectos, sino que constituye un tema habitual en entrevistas técnicas.

Dato Clave

Pinia es la solución oficial de gestión de estado para Vue 3. Vuex 5 fue cancelado, y Evan You se ha referido a Pinia como el Vuex 5 de facto. Todos los proyectos nuevos en Vue 3 deben utilizar Pinia 3.

Diferencias Arquitectónicas Fundamentales entre Pinia y Vuex

La diferencia arquitectónica central entre Pinia y Vuex radica en la forma en que ocurren las mutaciones de estado. Vuex impone un flujo de datos unidireccional estricto: los componentes despachan acciones, las acciones ejecutan mutations, y las mutations modifican el estado. Pinia elimina por completo la capa de mutations, permitiendo la modificación directa del estado desde acciones o incluso desde los propios componentes.

Esta simplificación reduce el código repetitivo en aproximadamente un 40% en la mayoría de los proyectos. Donde Vuex requiere cuatro conceptos (state, getters, mutations, actions), Pinia opera con tres (state, getters, actions).

| Característica | Pinia 3 | Vuex 4 | |----------------|---------|--------| | Mutations | Ninguna (cambios directos de estado) | Obligatorias para cambiar el estado | | TypeScript | Inferencia completa, sin augmentation | Augmentation manual de tipos necesaria | | Arquitectura de stores | Múltiples stores planos | Store único con módulos anidados | | Composition API | Soporte nativo | Basado en Options API | | Tamaño del bundle | ~1 KB gzipped | ~6 KB gzipped | | Vue Devtools | Soporte completo (v7) | Soporte completo | | SSR | Integrado | Requiere configuración | | Hot Module Replacement | Integrado | Configuración manual |

Definición de Stores: Options API vs Sintaxis Setup

Pinia ofrece dos sintaxis para definir stores. La sintaxis Options refleja la estructura de Vuex, facilitando la migración. La sintaxis Setup aprovecha la Composition API de Vue 3 para obtener máxima flexibilidad.

stores/counter-options.tstypescript
import { defineStore } from 'pinia'

// Sintaxis Options Store — familiar para desarrolladores Vuex
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    lastUpdated: null as Date | null,
  }),
  getters: {
    // Los getters reciben state como primer argumento con inferencia de tipos
    doubleCount: (state) => state.count * 2,
    isPositive(): boolean {
      // Acceso a otros getters via `this`
      return this.count > 0
    },
  },
  actions: {
    increment() {
      // Modificación directa del estado — sin commit() necesario
      this.count++
      this.lastUpdated = new Date()
    },
    async fetchCount(id: string) {
      // Las acciones asíncronas funcionan sin configuración extra
      const response = await fetch(`/api/counters/${id}`)
      const data = await response.json()
      this.count = data.count
    },
  },
})

La sintaxis Options establece una correspondencia directa con los conceptos de Vuex. El state reemplaza el state de Vuex, los getters permanecen como getters, y las actions absorben tanto las actions como las mutations de Vuex.

stores/counter-setup.tstypescript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// Sintaxis Setup Store — patrones idénticos a la Composition API
export const useCounterStore = defineStore('counter', () => {
  // ref() se convierte en state
  const count = ref(0)
  const lastUpdated = ref<Date | null>(null)

  // computed() se convierte en getters
  const doubleCount = computed(() => count.value * 2)
  const isPositive = computed(() => count.value > 0)

  // Las funciones se convierten en actions
  function increment() {
    count.value++
    lastUpdated.value = new Date()
  }

  async function fetchCount(id: string) {
    const response = await fetch(`/api/counters/${id}`)
    const data = await response.json()
    count.value = data.count
  }

  // Se debe retornar todo el state, getters y actions
  return { count, lastUpdated, doubleCount, isPositive, increment, fetchCount }
})

La sintaxis Setup proporciona flexibilidad total: watchers, reutilización de composables y lógica condicional funcionan de manera natural dentro de la definición del store. La contrapartida es que cada propiedad reactiva y cada método deben retornarse explícitamente.

Integración TypeScript: Donde Pinia Supera a Vuex

El soporte de TypeScript es el terreno donde Pinia gana de forma contundente. Vuex 4 requiere augmentation manual de tipos mediante declaraciones de módulos, y los módulos anidados complejos hacen que la inferencia de tipos sea frágil. Pinia infiere los tipos automáticamente a partir de la definición del store.

typescript
// Vuex 4 — augmentation manual de tipos requerida
import { Store } from 'vuex'

declare module 'vuex' {
  interface State {
    counter: {
      count: number
      lastUpdated: Date | null
    }
  }
}

// Acceder al state requiere assertions de tipo o helpers personalizados
const count = (store.state as { counter: { count: number } }).counter.count
typescript
// Pinia — inferencia de tipos completa, cero configuración
const counter = useCounterStore()

// TypeScript sabe que counter.count es number
// TypeScript sabe que counter.doubleCount es number
// TypeScript sabe que counter.increment() retorna void
// TypeScript sabe que counter.fetchCount() retorna Promise<void>
counter.increment()
console.log(counter.doubleCount)

Pinia también soporta stores genéricos para patrones reutilizables, una capacidad que requiere un boilerplate considerable en Vuex.

Composition API e Integración de Composables

Los stores Setup de Pinia aceptan cualquier composable de Vue, habilitando patrones avanzados como utilidades compartidas de VueUse o estado consciente del router.

stores/search.tstypescript
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'
import { useRoute } from 'vue-router'

export const useSearchStore = defineStore('search', () => {
  const route = useRoute()
  const query = ref('')
  const results = ref<SearchResult[]>([])
  const isLoading = ref(false)

  // Composable de VueUse utilizado directamente dentro del store
  const debouncedSearch = useDebounceFn(async (term: string) => {
    if (!term.trim()) {
      results.value = []
      return
    }
    isLoading.value = true
    try {
      const res = await fetch(`/api/search?q=${encodeURIComponent(term)}`)
      results.value = await res.json()
    } finally {
      isLoading.value = false
    }
  }, 300)

  // El watcher sincroniza el parámetro de URL con el estado del store
  watch(() => route.query.q, (q) => {
    if (typeof q === 'string') {
      query.value = q
      debouncedSearch(q)
    }
  })

  function setQuery(term: string) {
    query.value = term
    debouncedSearch(term)
  }

  return { query, results, isLoading, setQuery }
})

Este patrón resulta imposible en Vuex sin soluciones alternativas. Los módulos de Vuex no pueden utilizar composables, watchers ni hooks del router dentro de su definición. La lógica del store debe dividirse entre el módulo Vuex y funciones utilitarias externas.

¿Listo para aprobar tus entrevistas de Vue.js / Nuxt.js?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Comunicación entre Stores y Referencias Cruzadas

Vuex utiliza un store raíz único con módulos con namespace. El acceso al estado entre módulos requiere los parámetros verbosos rootState y rootGetters. Los stores de Pinia son independientes por diseño, y la comunicación entre stores utiliza imports directos.

stores/cart.tstypescript
import { defineStore } from 'pinia'
import { useProductStore } from './products'
import { useAuthStore } from './auth'
import { ref, computed } from 'vue'

export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])

  const total = computed(() => {
    // Acceso a otro store llamando a su composable
    const productStore = useProductStore()
    return items.value.reduce((sum, item) => {
      const product = productStore.getById(item.productId)
      return sum + (product?.price ?? 0) * item.quantity
    }, 0)
  })

  async function checkout() {
    const auth = useAuthStore()
    if (!auth.isAuthenticated) {
      throw new Error('Authentication required')
    }
    // Lógica de checkout
  }

  return { items, total, checkout }
})

Esta arquitectura de stores planos evita los árboles de módulos profundamente anidados que hacen que las aplicaciones Vuex de gran tamaño sean difíciles de navegar y refactorizar.

Migración de Vuex 4 a Pinia 3

La migración de Vuex a Pinia puede realizarse módulo por módulo. Ambas bibliotecas pueden coexistir durante el período de transición. La estrategia recomendada consiste en comenzar con los módulos hoja (stores de los que ningún otro módulo depende) y avanzar hacia adentro.

Compatibilidad de Migración

Pinia y Vuex pueden ejecutarse lado a lado en la misma aplicación. Se instala Pinia junto a Vuex, se migra un módulo a la vez y se elimina Vuex solo después de convertir todos los módulos.

typescript
// Antes: módulo Vuex
// store/modules/user.ts
const userModule = {
  namespaced: true,
  state: () => ({
    profile: null as UserProfile | null,
    preferences: {} as UserPreferences,
  }),
  mutations: {
    SET_PROFILE(state, profile: UserProfile) {
      state.profile = profile
    },
    UPDATE_PREFERENCES(state, prefs: Partial<UserPreferences>) {
      state.preferences = { ...state.preferences, ...prefs }
    },
  },
  actions: {
    async fetchProfile({ commit }) {
      const profile = await api.getProfile()
      commit('SET_PROFILE', profile)
    },
    async updatePreferences({ commit }, prefs: Partial<UserPreferences>) {
      await api.updatePreferences(prefs)
      commit('UPDATE_PREFERENCES', prefs)
    },
  },
  getters: {
    isLoggedIn: (state) => state.profile !== null,
    displayName: (state) => state.profile?.name ?? 'Guest',
  },
}
typescript
// Después: store Pinia
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { api } from '@/lib/api'

export const useUserStore = defineStore('user', () => {
  const profile = ref<UserProfile | null>(null)
  const preferences = ref<UserPreferences>({})

  const isLoggedIn = computed(() => profile.value !== null)
  const displayName = computed(() => profile.value?.name ?? 'Guest')

  async function fetchProfile() {
    profile.value = await api.getProfile()
  }

  async function updatePreferences(prefs: Partial<UserPreferences>) {
    await api.updatePreferences(prefs)
    Object.assign(preferences.value, prefs)
  }

  return { profile, preferences, isLoggedIn, displayName, fetchProfile, updatePreferences }
})

El patrón de migración es consistente: las mutations se transforman en asignaciones directas dentro de las actions, las llamadas a commit() desaparecen, y los helpers mapState/mapGetters se reemplazan por la desestructuración del composable del store.

Hidratación SSR con Pinia y Nuxt 4

El renderizado del lado del servidor introduce complejidad en la gestión de estado. Pinia maneja la serialización y la hidratación del estado en SSR de forma automática en Nuxt 4, mientras que Vuex requiere el manejo manual de window.__INITIAL_STATE__.

Trampa SSR

Los stores Setup que utilizan composables como useRoute() o useFetch() necesitan un manejo cuidadoso en contextos SSR. Estos composables solo deben invocarse durante la fase de setup, nunca dentro de callbacks asíncronos.

stores/products.ts — Store Pinia SSR-safe para Nuxt 4typescript
import { defineStore } from 'pinia'

export const useProductStore = defineStore('products', {
  state: () => ({
    items: [] as Product[],
    selectedCategory: 'all',
  }),
  actions: {
    async fetchProducts() {
      // useFetch es específico de Nuxt — usar $fetch para acciones del store
      this.items = await $fetch<Product[]>('/api/products', {
        query: { category: this.selectedCategory },
      })
    },
  },
  getters: {
    getById: (state) => (id: string) =>
      state.items.find((item) => item.id === id),
    filteredCount: (state) =>
      state.items.filter((p) => p.inStock).length,
  },
})

Nuxt 4 serializa automáticamente el estado de Pinia en el servidor y lo hidrata en el cliente. No se necesita lógica manual de replaceState() ni de deshidratación.

Comparación de Rendimiento e Impacto en el Bundle

El peso de Pinia (~1 KB gzipped) es aproximadamente 6 veces menor que el de Vuex (~6 KB). En aplicaciones con code splitting, los stores de Pinia son tree-shakables: los stores no utilizados y sus dependencias quedan excluidos de los bundles de producción.

Las diferencias de rendimiento en tiempo de ejecución son insignificantes para la mayoría de las aplicaciones. Ambas bibliotecas utilizan el sistema de reactividad de Vue internamente. La ganancia de rendimiento práctica proviene de la reducción del boilerplate, que genera menos re-renders causados por suscripciones demasiado amplias — el modelo de stores planos de Pinia incentiva stores granulares, lo que significa que los componentes se suscriben a menos estado.

Con la introducción del Vapor Mode en Vue 3.6, tanto Pinia como Vuex se benefician del pipeline de renderizado más rápido. Sin embargo, la integración más estrecha de Pinia con la Composition API lo posiciona mejor para las optimizaciones del Vapor Mode.

Preguntas de Entrevista Frecuentes sobre Gestión de Estado en Vue

Las entrevistas técnicas evalúan con frecuencia los conocimientos de gestión de estado. A continuación se presentan las preguntas que aparecen con mayor frecuencia en entrevistas para desarrolladores Vue en 2026.

P: ¿Por qué se eliminó la capa de mutations de Vuex en Pinia?

Las mutations existían en Vuex para habilitar la depuración con viaje en el tiempo (time-travel debugging) en DevTools. Cada cambio de estado debía pasar por una mutation síncrona para que DevTools pudiera registrar snapshots. Pinia logra la misma capacidad de depuración rastreando los cambios de estado a nivel reactivo, haciendo innecesaria la capa de mutations explícita. El resultado: menos boilerplate, misma capacidad de depuración.

P: ¿Cuándo se debe preferir un store Setup sobre un store Options?

Los stores Setup son la mejor opción cuando el store necesita composables (useRoute, useDebounceFn), watchers complejos o lógica compartida proveniente de funciones composables externas. Los stores Options funcionan bien para state CRUD sencillo con getters simples. En la práctica, los stores Setup son más comunes en bases de código de producción porque replican los patrones de la Composition API de los componentes.

P: ¿Cómo maneja Pinia la reactividad en objetos anidados?

Pinia utiliza reactive() de Vue para el objeto state completo y ref() para propiedades individuales en stores Setup. La reactividad profunda se aplica por defecto — las mutaciones de objetos anidados se rastrean automáticamente. Para casos sensibles al rendimiento con grandes conjuntos de datos, shallowRef() permite desactivar la reactividad profunda.

P: Explicar las dependencias entre stores en Pinia vs Vuex.

En Vuex, el acceso entre módulos utiliza los parámetros rootState y rootGetters en las actions, creando un acoplamiento implícito. En Pinia, los stores se importan mutuamente de forma directa mediante llamadas a useOtherStore(). Esto hace que las dependencias sean explícitas y permite que TypeScript las verifique en tiempo de compilación. Las dependencias circulares funcionan en Pinia siempre que no se invoquen durante la fase de setup inicial.

P: ¿Qué sucede con el estado de Pinia durante la hidratación SSR?

Durante el SSR, Pinia serializa todos los estados de los stores activos a JSON e incrusta el resultado en el payload HTML. En el cliente, Pinia hidrata cada store antes de que los componentes se monten. Cualquier estado establecido durante el renderizado del lado del servidor está disponible de inmediato en el cliente sin llamadas API duplicadas. En Nuxt 4, este proceso es completamente automático.

Para profundizar en la preparación de entrevistas Vue y Nuxt, se puede consultar el módulo de entrevista sobre gestión de estado o revisar el módulo sobre composables de Vue.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Conclusión

  • Pinia 3 es la biblioteca oficial de gestión de estado de Vue — Vuex se encuentra en modo mantenimiento sin versión 5 planificada
  • La capa de mutations desapareció: las actions de Pinia modifican el estado directamente, reduciendo el boilerplate en aproximadamente un 40%
  • La inferencia de TypeScript funciona de forma nativa en Pinia sin augmentation alguna, a diferencia de las declaraciones manuales de tipos de Vuex
  • Los stores Setup se integran perfectamente con la Composition API de Vue 3, habilitando la reutilización de composables y watchers dentro de los stores
  • La arquitectura de stores planos reemplaza el árbol de módulos anidados de Vuex, haciendo la comunicación entre stores explícita y type-safe
  • La migración puede realizarse de manera incremental: Pinia y Vuex coexisten, permitiendo la conversión módulo por módulo
  • La hidratación SSR es automática en Nuxt 4 con Pinia — sin necesidad de serialización manual
  • Para todos los nuevos proyectos Vue 3 en 2026, Pinia es la elección clara gracias a una mejor DX, bundles más livianos y un soporte del ecosistema más sólido

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Etiquetas

#vue
#pinia
#vuex
#state-management
#interview

Compartir

Artículos relacionados