chore(brain): ASTRA 성장 자산 동기화 — 기능 인벤토리·growth(약점프로필/학습큐)·일화기억·장기기억·회의록 원문
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
---
|
||||
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 |
|
||||
Reference in New Issue
Block a user