Vue State

Pinia

Pinia es el store oficial de Vue: simple, type-safe y construido sobre la Composition API. Estado, getters y acciones en un solo lugar, sin providers ni boilerplate.

intermediate12 min readUpdated 15 sept 2026
cart.js
js
// stores/cart.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";

export const useCartStore = defineStore("cart", () => {
  const items = ref([]);

  const total = computed(() =>
    items.value.reduce((sum, item) => sum + item.price, 0),
  );

  function add(item) {
    items.value.push(item);
  }

  return { items, total, add };
});
Mantenido por
El equipo de Vue
Construido sobre
La Composition API
Stores
defineStore
Estado
ref / reactive
Derivados
computed
Devtools
Soporte para time-travel

Por que importa

Por qué Pinia reemplazó a Vuex

Simple por diseño

Un store es una función que devuelve estado y acciones. No hay mutations, módulos ni providers que aprender.

Type-safe

Pinia está escrito pensando en TypeScript, por lo que el estado y las acciones infieren sus tipos casi sin anotaciones adicionales.

Nativo de la Composition API

Los setup stores utilizan ref, computed y funciones directamente, por lo que el código del store se ve igual que el resto de tus componentes.

La imagen completa

Las tres partes de un store de Pinia

Estado, getters y acciones. En un setup store, son simplemente refs, computeds y funciones.

Estado

Datos

Valores reactivos declarados con ref o reactive y devueltos desde el store.

Getters

Derivación

Valores computados derivados del estado, cacheados y compartidos entre componentes.

Acciones

Actualizaciones

Funciones que cambian el estado, ejecutan lógica asíncrona y pueden llamar a otros stores.

Pinia de un vistazo

El núcleo de Pinia

defineStore

Crea un store con un id único y ya sea una función de setup o de opciones.

Estado

Valores reactivos que contienen los datos que comparte tu aplicación.

Getters

Valores computados derivados del estado, cacheados automáticamente.

Acciones

Funciones que actualizan el estado y pueden ser asíncronas.

Plugins

Añade persistencia, logging u otros comportamientos a cada store.

Stores en componentes

Llama al hook del store y accede a su estado directamente, sin necesidad de providers.

Una breve historia

De Vuex al store oficial

  1. 2019

    Se introduce Pinia

    Aparece un store más ligero y compatible con la Composition API como alternativa a Vuex.

    19
  2. 2020

    Vuex 4

    Vuex se lanza para Vue 3, pero su sistema de módulos y mutations se sienten pesados.

    20
  3. 2022

    Oficial para Vue 3

    Pinia se convierte en el store oficialmente recomendado y se incluye en los nuevos proyectos de Vue.

    22
  4. 2023

    Integración con Nuxt

    Nuxt 3 lanza soporte para Pinia y auto-importación de stores.

    23
  5. Hoy

    El estándar de Vue

    Pinia es la solución de estado estándar para aplicaciones de Vue y Nuxt.

    Hoy

La guia completa

Pinia: Todo lo que necesitas saber

¿Qué es Pinia?

Pinia es la librería oficial de gestión de estado para Vue. Sustituyó a Vuex como el store recomendado en Vue 3 y ahora es la opción predeterminada en los nuevos proyectos de Vue y Nuxt. Es ligera, type-safe y está construida directamente sobre la Composition API.

Su diseño es intencionalmente minimalista. Un store es una función que devuelve el estado, valores derivados y acciones. No existen las mutations, ni el namespacing de módulos, ni un provider por store. Si conoces ref, computed y las funciones, ya conoces la mayor parte de Pinia.

Definiendo un store

Un store se crea con defineStore, que recibe un id único y una función de configuración (setup function).

// stores/cart.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";

export const useCartStore = defineStore("cart", () => {
  const items = ref([]);

  const total = computed(() =>
    items.value.reduce((sum, item) => sum + item.price, 0),
  );

  const count = computed(() => items.value.length);

  function add(item) {
    items.value.push(item);
  }

  function remove(id) {
    items.value = items.value.filter((item) => item.id !== id);
  }

  return { items, total, count, add, remove };
});

Este es un setup store: ref se convierte en el estado (state), computed se convierte en un getter y las funciones se convierten en acciones. El id único sirve para nombrar el store en las devtools y es obligatorio.

Pinia también soporta un options store si prefieres una estructura similar a la de Vuex:

// stores/counter.js
export const useCounter = defineStore("counter", {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count += 1;
    },
  },
});

Ambas formas están totalmente soportadas. Los setup stores suelen encajar mejor con TypeScript y la Composition API, mientras que los options stores pueden resultar más familiares para los usuarios de Vuex.

Uso de un store en componentes

Llama a la función del store para obtener la instancia del store. No es necesario utilizar ningún provider para envolver tus componentes.

<!-- Cart.vue -->
<script setup>
import { storeToRefs } from "pinia";
import { useCartStore } from "@/stores/cart";

const cart = useCartStore();
const { items, total } = storeToRefs(cart);
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }} — {{ item.price }}
      <button @click="cart.remove(item.id)">Remove</button>
    </li>
  </ul>
  <p>Total: {{ total }}</p>
</template>

La lectura de cart.total directamente en una plantilla se mantiene reactiva. Cuando quieras hacer destructuring del estado o de los getters manteniendo la reactividad, utiliza storeToRefs, que los convierte en refs. Las acciones pueden desestructurarse directamente ya que no requieren reactividad.

Getters

Los getters son valores computados derivados del estado. Se almacenan en caché y se comparten, por lo que cada componente que lee el mismo getter comparte un único cálculo.

// stores/products.js
export const useProductsStore = defineStore("products", () => {
  const products = ref([]);
  const filter = ref("");

  const visible = computed(() =>
    products.value.filter((p) =>
      p.name.toLowerCase().includes(filter.value.toLowerCase()),
    ),
  );

  const inStock = computed(() =>
    visible.value.filter((p) => p.stock > 0),
  );

  return { products, filter, visible, inStock };
});

Intentar usar watch para computar un valor es un error común. Si el valor puede derivarse del estado existente, debería ser un getter, exactamente igual a como usarías computed en un componente.

Acciones

Las acciones son funciones que modifican el estado. Pueden ser síncronas o asíncronas, y pueden llamar a otras acciones o incluso a otros stores.

// stores/users.js
export const useUsersStore = defineStore("users", () => {
  const users = ref([]);
  const loading = ref(false);
  const error = ref(null);

  async function fetchUsers() {
    loading.value = true;
    error.value = null;
    try {
      const res = await fetch("/api/users");
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      users.value = await res.json();
    } catch (err) {
      error.value = err.message;
    } finally {
      loading.value = false;
    }
  }

  return { users, loading, error, fetchUsers };
});

Sin embargo, para los datos del servidor, considera utilizar una capa de obtención de datos como TanStack Query, que gestiona el almacenamiento en caché y la invalidación. Pinia es ideal para el estado del cliente: carritos, filtros, preferencias de UI, estado de autenticación y borradores.

Plugins y persistencia

Los plugins de Pinia se ejecutan para cada store y pueden añadir funcionalidades como persistencia, logging o reseteo.

// persist.js
export function persistPlugin({ store }) {
  const saved = localStorage.getItem(store.$id);
  if (saved) store.$patch(JSON.parse(saved));

  store.$subscribe((_mutation, state) => {
    localStorage.setItem(store.$id, JSON.stringify(state));
  });
}

// main.js
const pinia = createPinia();
pinia.use(persistPlugin);
app.use(pinia);

El popular pinia-plugin-persistedstate hace lo mismo, ofreciendo opciones sobre qué claves almacenar. La persistencia es una de las razones principales para recurrir a un store en lugar del estado local de un componente.

Pinia con Nuxt

En Nuxt, instala el módulo oficial y los stores en stores/ se importarán automáticamente.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@pinia/nuxt"],
});
<!-- pages/cart.vue -->
<script setup>
const cart = useCartStore(); // auto-imported
</script>

Debido a que Nuxt gestiona la instalación y el renderizado en el servidor, el mismo store funciona tanto en el servidor como en el cliente. Consulta la guía de Nuxt para ver cómo se integra esto con useFetch y las rutas de servidor.

Mejores prácticas

  • Prefiere los setup stores para el código nuevo, especialmente con TypeScript.
  • Mantén los datos derivados en getters en lugar de observar el estado.
  • Usa storeToRefs al desestructurar el estado o los getters.
  • Mantén los datos del servidor en una capa de obtención de datos (data-fetching), no en Pinia.
  • Usa acciones para cualquier cosa que modifique el estado, incluyendo tareas asíncronas.
  • Persiste únicamente el estado que deba sobrevivir a una recarga de página.
  • Nombra los stores de forma clara según el dominio al que pertenezcan.

Errores comunes

  • Desestructurar el estado sin storeToRefs y perder la reactividad.
  • Usar watch para calcular un valor que debería derivarse mediante un getter.
  • Guardar datos obtenidos del servidor en el store y gestionar manualmente los flags de carga.
  • Olvidar instalar Pinia, provocando que las llamadas al store fallen en tiempo de ejecución.
  • Crear un único store enorme en lugar de stores enfocados en dominios específicos.
  • Mutar el estado desde fuera de una acción de una manera que sea difícil de rastrear.

Próximos pasos

Pinia es el equivalente en Vue de un store pequeño y moderno. Profundiza tus conocimientos de Vue, añade Nuxt para el renderizado en el servidor y las auto-importaciones, y compara este enfoque con la Context API de React, Zustand y Redux Toolkit. Después, construye un store pequeño con un getter, una acción asíncrona y persistencia para ver la poca cantidad de código que requiere.

Definiendo un store

Un setup store utiliza la misma sintaxis de ref, computed y funciones que tus componentes, por lo que hay menos que aprender.

Setup store
export const useCounter = defineStore(
  "counter",
  () => {
    const count = ref(0);
    const double = computed(
      () => count.value * 2,
    );
    const inc = () => count.value++;
    return { count, double, inc };
  },
);
Options store
export const useCounter = defineStore(
  "counter",
  {
    state: () => ({ count: 0 }),
    getters: {
      double: (s) => s.count * 2,
    },
    actions: {
      inc() { this.count++; },
    },
  },
);

Valores derivados

Usa getters (computed) para valores derivados del estado. No uses watch sobre el estado solo para guardar un valor calculado.

Preferido
const total = computed(() =>
  items.value.reduce(
    (sum, i) => sum + i.price, 0,
  ),
);
Evitar
const total = ref(0);
watch(items, () => {
  total.value = items.value.reduce(
    (sum, i) => sum + i.price, 0,
  );
});

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender Pinia?

Nuestro tutorial interactivo te guia a traves de Pinia paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.