본문으로 건너뛰기
Previous
Next
Pinia Complete Guide

Pinia Complete Guide

Pinia Complete Guide

이 글의 핵심

Pinia is the official state management library for Vue 3. It's simpler than Vuex, fully supports TypeScript, and works seamlessly with the Composition API.

Introduction

Pinia is the official state management library for Vue 3, succeeding Vuex. It provides a simpler API, excellent TypeScript support, and seamless integration with the Composition API.

Announced as the official state management solution for Vue at Vue.js Amsterdam 2022, Pinia has replaced Vuex as the recommended choice. The Vue core team no longer actively develops Vuex 5, instead focusing efforts on Pinia.

Why Pinia?

Vuex (Old Way):

// Complex setup with mutations
mutations: {
  INCREMENT(state) {
    state.count++;
  }
},
actions: {
  increment({ commit }) {
    commit('INCREMENT');
  }
}

Pinia (Modern Way):

// Direct state mutation
const store = useCounterStore();
store.count++;

Real-World Adoption

Pinia is now the standard for Vue 3 projects:

  • Nuxt 3 ships with Pinia integration by default (@pinia/nuxt)
  • Vite official templates include Pinia as an option
  • State of Vue 2023: 71% satisfaction rating among Vue developers
  • ~500k weekly npm downloads (growing rapidly since Vue 3 adoption increased)

Why developers migrated from Vuex to Pinia:

  • 50% less boilerplate ??no mutations, no modules namespace complexity
  • TypeScript-first design ??full type inference without manual type definitions
  • Composition API native ??feels natural if you use <script setup>
  • Modular by default ??each store is independent, no global store registry
  • Better DevTools ??Vue DevTools shows store history, time-travel debugging

When to use Pinia:

  • Any new Vue 3 project
  • Migrating from Vuex (straightforward migration path)
  • Need TypeScript support
  • Using Composition API

When NOT to use Pinia:

  • Vue 2 projects without Composition API (stick with Vuex 3)
  • Extremely simple apps (Vue’s reactive state with reactive() might be enough)
  • Need Vuex-specific plugins that don’t have Pinia equivalents yet

1. Installation & Setup

Install Pinia

npm install pinia

Configure in main.ts

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');

2. Defining Stores

Option Stores (Similar to Vuex)

// stores/counter.ts
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'Counter',
  }),
  
  getters: {
    doubleCount: (state) => state.count * 2,
    
    // With parameters
    countPlusN: (state) => {
      return (n: number) => state.count + n;
    },
  },
  
  actions: {
    increment() {
      this.count++;
    },
    
    async fetchCount() {
      const response = await fetch('/api/count');
      const data = await response.json();
      this.count = data.count;
    },
  },
});

Setup Stores (Composition API Style)

// stores/user.ts
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', () => {
  // State
  const user = ref<User | null>(null);
  const isAuthenticated = ref(false);
  
  // Getters
  const displayName = computed(() => user.value?.name ?? 'Guest');
  
  // Actions
  async function login(email: string, password: string) {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    
    const data = await response.json();
    user.value = data.user;
    isAuthenticated.value = true;
  }
  
  function logout() {
    user.value = null;
    isAuthenticated.value = false;
  }
  
  return {
    user,
    isAuthenticated,
    displayName,
    login,
    logout,
  };
});

3. Using Stores in Components

In Composition API

<script setup lang="ts">
import { useCounterStore } from '@/stores/counter';

const counter = useCounterStore();

// Direct access
console.log(counter.count);

// Call actions
counter.increment();

// Reactive destructuring
import { storeToRefs } from 'pinia';
const { count, doubleCount } = storeToRefs(counter);
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="counter.increment">Increment</button>
  </div>
</template>

In Options API

<script lang="ts">
import { useCounterStore } from '@/stores/counter';
import { mapStores, mapState, mapActions } from 'pinia';

export default {
  computed: {
    ...mapStores(useCounterStore),
    ...mapState(useCounterStore, ['count', 'doubleCount']),
  },
  
  methods: {
    ...mapActions(useCounterStore, ['increment']),
  },
};
</script>

<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

4. TypeScript Integration

// types/store.ts
export interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

// stores/auth.ts
import type { User } from '@/types/store';

export const useAuthStore = defineStore('auth', () => {
  const user = ref<User | null>(null);
  const token = ref<string | null>(null);
  
  const isAdmin = computed(() => user.value?.role === 'admin');
  
  async function login(credentials: { email: string; password: string }) {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(credentials),
    });
    
    if (!response.ok) {
      throw new Error('Login failed');
    }
    
    const data = await response.json();
    user.value = data.user;
    token.value = data.token;
  }
  
  return {
    user,
    token,
    isAdmin,
    login,
  };
});

5. Actions with Async/Await

export const useProductStore = defineStore('products', () => {
  const products = ref<Product[]>([]);
  const loading = ref(false);
  const error = ref<string | null>(null);
  
  async function fetchProducts() {
    loading.value = true;
    error.value = null;
    
    try {
      const response = await fetch('/api/products');
      products.value = await response.json();
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Unknown error';
    } finally {
      loading.value = false;
    }
  }
  
  async function createProduct(product: Omit<Product, 'id'>) {
    const response = await fetch('/api/products', {
      method: 'POST',
      body: JSON.stringify(product),
    });
    
    const newProduct = await response.json();
    products.value.push(newProduct);
    return newProduct;
  }
  
  return {
    products,
    loading,
    error,
    fetchProducts,
    createProduct,
  };
});

6. Store Plugins

// plugins/persist.ts
import { PiniaPluginContext } from 'pinia';

export function piniaPersistedState({ store }: PiniaPluginContext) {
  // Load from localStorage
  const saved = localStorage.getItem(store.$id);
  if (saved) {
    store.$patch(JSON.parse(saved));
  }
  
  // Save on change
  store.$subscribe((mutation, state) => {
    localStorage.setItem(store.$id, JSON.stringify(state));
  });
}

// main.ts
const pinia = createPinia();
pinia.use(piniaPersistedState);

7. Subscribing to Changes

const counter = useCounterStore();

// Subscribe to state changes
counter.$subscribe((mutation, state) => {
  console.log('State changed:', state);
  
  // Save to localStorage
  localStorage.setItem('counter', JSON.stringify(state));
});

// Subscribe to actions
counter.$onAction(({ name, args, after, onError }) => {
  console.log(`Action ${name} called with`, args);
  
  after((result) => {
    console.log('Action completed:', result);
  });
  
  onError((error) => {
    console.error('Action failed:', error);
  });
});

8. Best Practices

1. Modular Stores

// One store per domain
stores/
?��??� auth.ts      # Authentication
?��??� cart.ts      # Shopping cart
?��??� products.ts  # Product catalog
?��??� ui.ts        # UI state (modals, toasts)

2. Store Composition

export const useCartStore = defineStore('cart', () => {
  const auth = useAuthStore();  // Use another store
  const items = ref([]);
  
  const canCheckout = computed(() => {
    return auth.isAuthenticated && items.value.length > 0;
  });
  
  return { items, canCheckout };
});

3. Reset Store State

const counter = useCounterStore();

// Reset to initial state
counter.$reset();

4. Patch Multiple Changes

// ??Multiple reactivity triggers
counter.count = 1;
counter.name = 'New name';

// ??Single reactivity trigger
counter.$patch({
  count: 1,
  name: 'New name',
});

// ??Function patch (better for complex logic)
counter.$patch((state) => {
  state.count++;
  state.items.push({ id: 1 });
});

9. Testing

import { setActivePinia, createPinia } from 'pinia';
import { useCounterStore } from '@/stores/counter';

describe('Counter Store', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
  });
  
  it('increments count', () => {
    const counter = useCounterStore();
    expect(counter.count).toBe(0);
    
    counter.increment();
    expect(counter.count).toBe(1);
  });
  
  it('doubles count', () => {
    const counter = useCounterStore();
    counter.count = 5;
    expect(counter.doubleCount).toBe(10);
  });
});

10. DevTools

Pinia integrates with Vue DevTools:

// Automatic in development
import { createPinia } from 'pinia';

const pinia = createPinia();

// DevTools will show:
// - All stores
// - State snapshots
// - Actions timeline
// - Mutations

Summary

Pinia modernizes Vue state management:

  • Simple API - no mutations required
  • TypeScript first - excellent type inference
  • Composition API - natural integration
  • Lightweight - only 1KB
  • Devtools - powerful debugging

Key Takeaways:

  1. Replace Vuex with Pinia for simpler code
  2. Use setup stores with Composition API
  3. Leverage TypeScript for type safety
  4. Create modular stores per domain
  5. Use plugins for cross-cutting concerns

Next Steps:

  • Learn [Vue complete guide](/en/blog/vue-complete-guide/
  • Compare with [Zustand](/en/blog/zustand-complete-guide/ (React)
  • Build full apps with [Nuxt 3](/en/blog/nuxt-3-complete-guide/

Resources:


?�주 묻는 질문 (FAQ)

Q. ???�용???�무?�서 ?�제 ?�나??

A. Complete Pinia guide for Vue 3 state management. Learn store definitions, Composition API patterns, TypeScript integrati???�무?�서????본문???�제?� ?�택 가?�드�?참고???�용?�면 ?�니??

Q. ?�행?�로 ?�으�?좋�? 글?�?

A. �?글 ?�단???�전 글 ?�는 관??글 링크�??�라가�??�서?��?배울 ???�습?�다. C++ ?�리�?목차?�서 ?�체 ?�름???�인?????�습?�다.

Q. ??깊이 공�??�려�?

A. cppreference?� ?�당 ?�이브러�?공식 문서�?참고?�세?? 글 말�???참고 ?�료 링크???�용?�면 좋습?�다.


같이 보면 좋�? 글 (?��? 링크)

??주제?� ?�결?�는 ?�른 글?�니??

  • [Redux Toolkit Complete Guide | Modern Redux State Management](/en/blog/redux-toolkit-complete-guide/
  • [tRPC Complete Guide | End-to-End TypeScript Type Safety](/en/blog/trpc-complete-guide/
  • [Yup Complete Guide | Schema Validation for JavaScript](/en/blog/yup-complete-guide/

??글?�서 ?�루???�워??(관??검?�어)

Pinia, Vue 3, State Management, Composition API, TypeScript, Frontend, Vuex ?�으�?검?�하?�면 ??글???��????�니??