f8b21af4be
10_Wiki/Topics 대규모 정리: - 오류 캡처/미완성 stub 문서 227개 제거 - 교차폴더 중복 43클러스터 병합 (63파일 → redirect) - 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건 - 카테고리 MOC 6개 신규 생성 - Graph 섹션 미해결 related-keyword 링크 10,058건 제거 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
230 lines
6.5 KiB
Markdown
230 lines
6.5 KiB
Markdown
---
|
||
id: wiki-2026-0508-pinia
|
||
title: Pinia
|
||
category: 10_Wiki/Topics
|
||
status: verified
|
||
canonical_id: self
|
||
aliases: [Vue Store, Pinia Store]
|
||
duplicate_of: none
|
||
source_trust_level: A
|
||
confidence_score: 0.9
|
||
verification_status: applied
|
||
tags: [vue, state-management, frontend]
|
||
raw_sources: []
|
||
last_reinforced: 2026-05-10
|
||
github_commit: pending
|
||
tech_stack:
|
||
language: TypeScript
|
||
framework: Vue 3 / Pinia 2
|
||
---
|
||
|
||
# Pinia
|
||
|
||
## 매 한 줄
|
||
> **"매 Vue 3 매 official state management — 매 Vuex 4 후속, 매 Composition API native + 매 TypeScript-first"**. Eduardo San Martin Morote 매 2019 발표 매 Vue Core 팀 채택, 매 store 매 composable function 매 표현 매 boilerplate 90% 감소, 매 2026 매 Vue 표준 store library.
|
||
|
||
## 매 핵심
|
||
|
||
### 매 vs Vuex
|
||
- **No mutations**: 매 actions 가 directly state 수정 — 매 mutation indirection 제거.
|
||
- **Flat stores**: 매 nested module 매 X — 매 cross-store import 매 graph 형성.
|
||
- **Type inference**: 매 zero manual typing — 매 store usage 매 fully typed.
|
||
- **Devtools**: 매 timeline + state inspect + time-travel 지원.
|
||
|
||
### 매 store kinds
|
||
- **Options Store**: `state`, `getters`, `actions` — 매 Vuex-like familiar shape.
|
||
- **Setup Store**: `ref`/`computed`/`function` — 매 Composition API native.
|
||
|
||
### 매 응용
|
||
1. SPA global state (auth, user prefs).
|
||
2. Server-side rendering (Nuxt 3 매 native integration).
|
||
3. Cross-component caching (API result, derived state).
|
||
4. Plugin extension (persistedstate, undo/redo).
|
||
|
||
## 💻 패턴
|
||
|
||
### Setup store (modern preferred)
|
||
```ts
|
||
// stores/counter.ts
|
||
import { defineStore } from 'pinia'
|
||
import { ref, computed } from 'vue'
|
||
|
||
export const useCounterStore = defineStore('counter', () => {
|
||
const count = ref(0)
|
||
const doubled = computed(() => count.value * 2)
|
||
function increment() { count.value++ }
|
||
function reset() { count.value = 0 }
|
||
return { count, doubled, increment, reset }
|
||
})
|
||
```
|
||
|
||
### Options store
|
||
```ts
|
||
export const useUserStore = defineStore('user', {
|
||
state: () => ({
|
||
user: null as User | null,
|
||
loading: false,
|
||
}),
|
||
getters: {
|
||
isLoggedIn: (s) => s.user !== null,
|
||
displayName: (s) => s.user?.name ?? 'Guest',
|
||
},
|
||
actions: {
|
||
async login(creds: Credentials) {
|
||
this.loading = true
|
||
try {
|
||
this.user = await api.login(creds)
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
logout() { this.user = null },
|
||
},
|
||
})
|
||
```
|
||
|
||
### Component usage
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { storeToRefs } from 'pinia'
|
||
import { useCounterStore } from '@/stores/counter'
|
||
|
||
const store = useCounterStore()
|
||
// 매 reactivity 매 유지: storeToRefs 매 ref 변환
|
||
const { count, doubled } = storeToRefs(store)
|
||
// actions 매 destructure OK (not reactive)
|
||
const { increment } = store
|
||
</script>
|
||
|
||
<template>
|
||
<button @click="increment">{{ count }} (×2 = {{ doubled }})</button>
|
||
</template>
|
||
```
|
||
|
||
### App setup (Vue 3)
|
||
```ts
|
||
// 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')
|
||
```
|
||
|
||
### Cross-store usage
|
||
```ts
|
||
import { useAuthStore } from './auth'
|
||
|
||
export const useCartStore = defineStore('cart', () => {
|
||
const auth = useAuthStore() // 매 다른 store 매 import + use
|
||
const items = ref<CartItem[]>([])
|
||
|
||
async function checkout() {
|
||
if (!auth.isLoggedIn) throw new Error('Login required')
|
||
return api.checkout(auth.user!.id, items.value)
|
||
}
|
||
return { items, checkout }
|
||
})
|
||
```
|
||
|
||
### $patch 매 batch update
|
||
```ts
|
||
const store = useUserStore()
|
||
store.$patch({ loading: false, user: newUser }) // 매 single devtools entry
|
||
// or function form for complex mutations
|
||
store.$patch((state) => {
|
||
state.cart.push(item)
|
||
state.lastUpdated = Date.now()
|
||
})
|
||
```
|
||
|
||
### $subscribe 매 store mutation 감지
|
||
```ts
|
||
store.$subscribe((mutation, state) => {
|
||
localStorage.setItem('cart', JSON.stringify(state))
|
||
}, { detached: true })
|
||
```
|
||
|
||
### Plugin (persistedstate)
|
||
```ts
|
||
import { createPinia } from 'pinia'
|
||
import piniaPersist from 'pinia-plugin-persistedstate'
|
||
|
||
const pinia = createPinia()
|
||
pinia.use(piniaPersist)
|
||
|
||
// store 정의 시:
|
||
export const useUserStore = defineStore('user', {
|
||
state: () => ({ token: '' }),
|
||
persist: true, // localStorage 매 자동 sync
|
||
})
|
||
```
|
||
|
||
### SSR (Nuxt 3)
|
||
```ts
|
||
// composables/useAuth.ts
|
||
export const useAuthStore = defineStore('auth', () => {
|
||
const user = ref<User | null>(null)
|
||
// 매 Nuxt 3 매 자동 hydration — server 매 set 한 state 매 client 매 transfer
|
||
return { user }
|
||
})
|
||
```
|
||
|
||
### Testing
|
||
```ts
|
||
import { setActivePinia, createPinia } from 'pinia'
|
||
import { beforeEach, expect, test } from 'vitest'
|
||
|
||
beforeEach(() => setActivePinia(createPinia()))
|
||
|
||
test('counter increments', () => {
|
||
const store = useCounterStore()
|
||
expect(store.count).toBe(0)
|
||
store.increment()
|
||
expect(store.count).toBe(1)
|
||
expect(store.doubled).toBe(2)
|
||
})
|
||
```
|
||
|
||
## 매 결정 기준
|
||
| 상황 | Approach |
|
||
|---|---|
|
||
| Vue 3 new project | Pinia (default) |
|
||
| Vuex 4 migration | Pinia (incremental, alias module) |
|
||
| Composition API preference | Setup store |
|
||
| Vuex-familiar team | Options store |
|
||
| Component-local state | `ref`/`reactive` (no store) |
|
||
| Server state caching | TanStack Query / 매 store 의 X |
|
||
| SSR (Nuxt) | Pinia (built-in support) |
|
||
|
||
**기본값**: 매 setup store + TypeScript + storeToRefs.
|
||
|
||
## 🔗 Graph
|
||
- 부모: [[State-Management]]
|
||
- 변형: [[Vuex]] (predecessor) · [[프론트엔드_및_UIUX_표준|Redux]] · [[Zustand]] · [[Jotai]]
|
||
- 응용: [[Nuxt]]
|
||
- Adjacent: [[Composition-API]]
|
||
|
||
## 🤖 LLM 활용
|
||
**언제**: 매 Vue 3 global state, 매 cross-component sharing, 매 Nuxt 3 SSR state, 매 Vuex migration target.
|
||
**언제 X**: 매 component-local state (ref 만 충분), 매 server cache (TanStack Query 적합), 매 React project (Zustand/Redux).
|
||
|
||
## ❌ 안티패턴
|
||
- **storeToRefs 없이 destructure**: 매 reactivity loss — 매 `const { count } = store` 의 X.
|
||
- **Store action 매 component logic 침범**: 매 store 매 단순 state holder 매 됨 — 매 business logic 매 store 에 응집.
|
||
- **Nested store hierarchy 시도**: 매 flat 의 X — 매 cross-import 매 graph 형성.
|
||
- **Mutation pattern 의 반복**: 매 Vuex habit — 매 action 에서 직접 state 수정 OK.
|
||
- **매 component 매 store instance 다중 생성**: 매 useStore() 매 singleton — 매 매 호출 동일 instance.
|
||
|
||
## 🧪 검증 / 중복
|
||
- Verified (Pinia 2.x docs, Vue.js official).
|
||
- 신뢰도 A.
|
||
|
||
## 🕓 Changelog
|
||
| 날짜 | 변경 |
|
||
|---|---|
|
||
| 2026-05-08 | Phase 1 |
|
||
| 2026-05-10 | Manual cleanup — setup/options store + cross-store + SSR + plugin patterns |
|