Inicio
Blogs
Series
Componentes
Diagrama de decisión para elegir entre estado local, props y eventos, composables o Pinia en Vue 3.

Pinia en Vue 3: cuándo usar estado global y cuándo no

Aprende a decidir si un dato debe vivir en un componente, un subárbol o una store de Pinia según sus consumidores, duración y lógica compartida.

Pinia en Vue 3: cuándo usar estado global y cuándo no

Pinia no vuelve escalable una aplicación por sí sola. Una store mal elegida puede ocultar quién posee un dato y convertir una relación sencilla entre componentes en una dependencia global.

La decisión empieza antes de escoger una herramienta: identifica quién consume el estado, quién lo modifica y cuánto tiempo debe vivir. Con esos criterios, Pinia se convierte en una frontera útil cuando el alcance realmente es global.

Antes de elegir Pinia: define el alcance del estado

Cada instancia de componente ya tiene estado reactivo propio. El estado compartido importa cuando varios componentes o vistas dependen del mismo dato, o cuando distintas acciones deben coordinar sus cambios.

Antes de crear una store, pregunta:

  • ¿Un único componente posee este dato?
  • ¿Solo lo coordinan componentes de una pantalla o subárbol?
  • ¿Debe sobrevivir a una navegación?
  • ¿Su cambio representa una operación de negocio reconocible?

Pinia es consecuencia de esas respuestas, no el punto de partida.

Mapa de decisión: local, subárbol o global

SituaciónAlcance recomendadoEjemplo
Un control visual de un componenteEstado local con ref() o reactive()Abrir un modal de una página
Varios descendientes de una pantallaAncestro común con props y eventosFiltros de catálogo
Comportamiento reutilizable por instanciaComposable con estado local por usoLógica de búsqueda
Datos y lógica entre componentes o páginasStore de PiniaSesión en la navegación
Proceso que continúa entre rutasStore de PiniaCheckout multipágina

Un composable concentra comportamiento reutilizable, pero no vuelve global su estado automáticamente. Decide por separado si cada uso necesita una instancia propia o si la aplicación requiere una fuente compartida.

Cuándo props y eventos son más claros que una store

En una relación padre-hijo, los props y eventos mantienen el flujo explícito: el padre entrega datos y el hijo comunica una intención. El hijo no debe mutar un prop.

Aquí los filtros pertenecen a la pantalla de catálogo. El ancestro conserva el estado y el control descendiente solicita el cambio; ninguna otra ruta necesita conocerlo.

<script setup>
import { ref } from 'vue'

const selectedCategory = ref('all')

function selectCategory(category) {
  selectedCategory.value = category
}
</script>
 
<template>
  <CategoryFilters
    :selected-category="selectedCategory"
    @select-category="selectCategory"
  />
</template>
<script setup>
defineProps({
  selectedCategory: {
    type: String,
    required: true,
  },
})

const emit = defineEmits(['select-category'])
</script>
 
<template>
  <button
    type="button"
    :aria-pressed="selectedCategory === 'books'"
    @click="emit('select-category', 'books')"
  >
    Books
  </button>
</template>

El padre declara el propietario real del estado y el hijo emite una intención sin modificar el prop. Si demasiados niveles intermedios vuelven incómodo el árbol, revisa el diseño y el alcance: el prop drilling es una señal para evaluar alternativas, no una orden automática de globalizar.

Cuándo Pinia sí gana su lugar

Una store de Pinia organiza estado y lógica de negocio fuera del árbol de componentes mediante state, getters y actions. Encaja cuando varias partes de la aplicación necesitan la misma fuente de verdad, como una sesión visible en navegación y páginas protegidas, o un formulario complejo que continúa entre páginas.

Una store pequeña debe tener una responsabilidad concreta. En una Option Store, declara todas las propiedades desde state(); no las agregues después.

stores/session.ts
import { defineStore } from 'pinia'
 
type SessionUser = { id: string; name: string }
 
export const useSessionStore = defineStore('session', {
  state: () => ({
    user: undefined as SessionUser | undefined,
  }),
 
  getters: {
    isAuthenticated: (state) => state.user !== undefined,
  },
 
  actions: {
    setUser(user: SessionUser) {
      this.user = user
    },
    clearSession() {
      this.user = undefined
    },
  },
})

user es estado almacenado e isAuthenticated es estado derivado, por lo que no se duplica como dato mutable. Las actions nombran operaciones de sesión y hacen más fácil localizar por qué cambia el estado compartido. Esto no implica que una action prohíba por sí misma toda mutación directa: la claridad proviene de aplicar y respetar esa frontera.

No uses esta store para la visibilidad de un tooltip o de un modal que solo existe en una página. Ese detalle pertenece al componente o, como máximo, a su pantalla.

Estado compartido simple y composables: alternativas con límites

Para un caso sencillo de una SPA, Vue permite compartir un objeto reactive() importado por varios componentes. El límite importante es el de modificación: si cualquier consumidor escribe cualquier propiedad, el diseño pierde trazabilidad.

catalogFilters.ts
import { reactive } from 'vue'
 
export const catalogFilters = reactive({
  category: 'all',
  setCategory(category: string) {
    this.category = category
  },
})

Centralizar el cambio en setCategory() comunica qué operación ocurre. Evalúa Pinia cuando el estado crece o cuando necesitas convenciones de colaboración, integración con DevTools, HMR o una frontera SSR más robusta.

SSR: no compartas estado entre solicitudes

En renderizado del lado del servidor, un singleton reactivo creado en ámbito de módulo puede compartir estado entre solicitudes. El aislamiento debe respetar la instancia de cada aplicación.

Pinia trabaja con la instancia de Pinia correspondiente. Si utilizas una store fuera de un componente, hazlo cuando esa instancia esté disponible y usa la asociada a la aplicación actual. Es una regla de aislamiento, no una receta de un framework concreto.

Errores comunes y una regla práctica

Evita globalizar cada variable reactiva, sustituir props y eventos en una relación padre-hijo sencilla, guardar detalles visuales efímeros de una página en una store, permitir mutaciones compartidas sin una operación con intención clara y crear un singleton manual para SSR.

La regla práctica es empezar con el alcance más pequeño que represente honestamente la propiedad del dato. Amplíalo cuando los consumidores, la duración entre páginas o la lógica de coordinación lo exijan. Pinia aporta valor cuando hace explícita una frontera global real.

Editar esta página en GitHub

¿Viste un error o quieres mejorar esta entrada? Puedes proponer cambios directamente.