Pinia 기초
Pinia는 Vue의 공식 상태 관리 라이브러리입니다. Vuex의 후속으로, 더 간결하고 TypeScript 친화적인 API를 제공합니다.
면접에서 "Vuex 대신 Pinia를 쓰는 이유"를 물어보면, mutations 제거, TypeScript 지원, 모듈 구조의 단순화를 핵심 포인트로 설명할 수 있어야 합니다.
설치와 설정
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
스토어 정의 — Setup 문법 (권장)
// stores/counter.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
// Setup Store — Composition API와 동일한 방식
export const useCounterStore = defineStore('counter', () => {
// state — ref()
const count = ref(0)
const name = ref('카운터')
// getters — computed()
const doubleCount = computed(() => count.value * 2)
const displayName = computed(() => `${name.value}: ${count.value}`)
// actions — function
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = 0
}
const incrementAsync = async () => {
await new Promise(resolve => setTimeout(resolve, 1000))
count.value++
}
return {
count, name,
doubleCount, displayName,
increment, decrement, reset, incrementAsync
}
})
스토어 정의 — Options 문법
// stores/user.ts
import { defineStore } from 'pinia'
interface User {
id: number
name: string
email: string
}
// Options Store — Vuex와 비슷한 구조
export const useUserStore = defineStore('user', {
state: () => ({
currentUser: null as User | null,
isLoggedIn: false,
preferences: {
theme: 'light' as 'light' | 'dark',
language: 'ko'
}
}),
getters: {
userName: (state) => state.currentUser?.name ?? '게스트',
// 다른 getter 사용
greeting(): string {
return `안녕하세요, ${this.userName}!`
}
},
actions: {
async login(email: string, password: string) {
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
})
this.currentUser = await response.json()
this.isLoggedIn = true
} catch (error) {
console.error('로그인 실패:', error)
throw error
}
},
logout() {
this.currentUser = null
this.isLoggedIn = false
},
setTheme(theme: 'light' | 'dark') {
this.preferences.theme = theme
}
}
})
컴포넌트에서 사용하기
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const counterStore = useCounterStore()
const userStore = useUserStore()
// 직접 접근 — 반응형 유지
console.log(counterStore.count)
console.log(counterStore.doubleCount)
// storeToRefs — 구조 분해 시 반응형 유지
// 주의: actions는 storeToRefs에 포함되지 않음
const { count, doubleCount, displayName } = storeToRefs(counterStore)
const { increment, decrement, reset } = counterStore // 액션은 직접 분해
</script>
<template>
<div>
<p>{{ displayName }}</p>
<p>두 배: {{ doubleCount }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="reset">리셋</button>
</div>
<div>
<p>{{ userStore.greeting }}</p>
<button @click="userStore.setTheme('dark')">다크 모드</button>
</div>
</template>
상태 변경 방법
const store = useCounterStore()
// 1. 직접 변경
store.count++
// 2. $patch — 여러 속성을 한번에 변경
store.$patch({
count: 10,
name: '새 카운터'
})
// 3. $patch 함수 — 복잡한 변경
store.$patch((state) => {
state.count += 5
state.name = `카운터 (${state.count})`
})
// 4. 액션 호출
store.increment()
// 5. $reset — 초기 상태로 리셋 (Options Store만)
// store.$reset()
Pinia vs Vuex
| 항목 | Vuex | Pinia |
|---|---|---|
| mutations | 필수 (상태 변경은 mutations으로) | 없음 (직접 변경 가능) |
| 모듈 | 중첩 모듈 + namespace | 각 스토어가 독립적 |
| TypeScript | 제한적 | 네이티브 지원 |
| Composition API | 제한적 | 완벽 지원 |
| 번들 크기 | ~10KB | ~1.5KB |
| DevTools | 지원 | 지원 |
스토어 구독 (상태 변경 감시)
const store = useCounterStore()
// $subscribe — 상태 변경 감시
store.$subscribe((mutation, state) => {
console.log('변경 타입:', mutation.type)
console.log('새 상태:', state)
// 로컬 스토리지에 자동 저장
localStorage.setItem('counter', JSON.stringify(state))
})
// $onAction — 액션 호출 감시
store.$onAction(({ name, args, after, onError }) => {
console.log(`액션 호출: ${name}`, args)
after((result) => {
console.log(`액션 완료: ${name}`, result)
})
onError((error) => {
console.error(`액션 에러: ${name}`, error)
})
})
면접 팁
- Pinia에서 mutations가 사라진 이유: "상태 추적은 DevTools가 자동으로 해주므로, mutations이라는 보일러플레이트가 불필요해졌습니다"
storeToRefs를 써야 하는 이유를 반응형 소실 문제와 연결해서 설명하세요- "언제 Pinia를 쓰고, 언제 컴포넌트 로컬 상태를 쓰나요?" — 여러 컴포넌트가 공유해야 하는 상태만 스토어에 넣는 것이 원칙입니다
요약
Pinia는 Vue 3의 공식 상태 관리 라이브러리로, Setup Store(Composition API 스타일)와 Options Store 두 가지 정의 방식을 제공합니다. mutations 없이 직접 상태를 변경할 수 있고, TypeScript를 완벽 지원합니다. storeToRefs로 구조 분해 시 반응형을 유지하는 것이 핵심입니다.
댓글 로딩 중...