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:
- Replace Vuex with Pinia for simpler code
- Use setup stores with Composition API
- Leverage TypeScript for type safety
- Create modular stores per domain
- 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 ?�으�?검?�하?�면 ??글???��????�니??