Files
fe-agent-new/skills/api-pinia-store/SKILL.md

148 lines
4.3 KiB
Markdown

---
name: api-pinia-store
description: Pinia 스토어를 팀 컨벤션에 맞게 생성할 때 사용합니다. "스토어 만들어줘", "Pinia 스토어", "전역 상태", "store 추가", "상태관리", "useAuthStore", "useCartStore" 등을 요청하면 트리거됩니다.
---
# Pinia 스토어 생성
이 skill은 Pinia 스토어를 팀 컨벤션에 맞게 생성합니다.
**Setup Store(Composition API) 문법**을 기본으로 사용합니다.
## 작업 순서
1. **스토어 필요성 판단**
- 아래 판단 가이드를 참고하여 스토어가 적합한지 확인
- 서버 데이터 → composable(`useFetch`), 로컬 상태 → `ref`/`reactive` 권장
2. **기존 스토어 탐색**
- `stores/` 디렉토리에서 기존 스토어 패턴 확인
- 중복 생성 방지, 기존 스토어 확장 가능 여부 검토
3. **상태 설계**
- TypeScript 인터페이스로 상태 형태 정의
- 최소한의 원시 상태만 저장, 파생 가능한 값은 `computed`
4. **Setup Store 구현**
- state → `ref`/`reactive`
- getters → `computed`
- actions → 일반 함수
- 모두 하나의 객체로 반환
5. **persistence 추가 (필요 시)**
- `pinia-plugin-persistedstate` 사용 여부 사용자 확인
6. **검증**
- TypeScript 오류 확인
- SSR hydration 호환성 확인
## 스토어 필요성 판단 가이드
| 상태의 성격 | 해결 방법 | 예시 |
| --- | --- | --- |
| 서버 데이터 (API 응답) | composable + `useFetch` | 상품 목록, 유저 프로필 |
| 앱 전역 공유 상태 | **Pinia 스토어** | 인증, 테마, 사이드바 상태 |
| 단일 페이지 폼 상태 | 로컬 `ref`/`reactive` | 회원가입 폼 |
| 부모↔자식 공유 상태 | props/emits 또는 `provide/inject` | 아코디언 그룹 |
| URL 기반 상태 | 라우트 쿼리/파라미터 | 필터, 정렬, 페이지 |
## Setup Store 템플릿
```ts
// stores/useAuthStore.ts
import type { User } from '~/types/user';
export const useAuthStore = defineStore('auth', () => {
// state
const user = ref<User | null>(null);
const token = ref<string | null>(null);
// getters
const isLoggedIn = computed(() => !!token.value);
const userName = computed(() => user.value?.name ?? '');
// actions
async function login(credentials: LoginCredentials) {
const response = await $fetch<AuthResponse>('/api/auth/login', {
method: 'POST',
body: credentials,
});
user.value = response.user;
token.value = response.token;
}
function logout() {
user.value = null;
token.value = null;
navigateTo('/login');
}
return {
// state
user,
token,
// getters
isLoggedIn,
userName,
// actions
login,
logout,
};
});
```
## UI 상태 스토어 템플릿
```ts
// stores/useUiStore.ts
export const useUiStore = defineStore('ui', () => {
const isSidebarOpen = ref(false);
const theme = ref<'light' | 'dark'>('light');
function toggleSidebar() {
isSidebarOpen.value = !isSidebarOpen.value;
}
function setTheme(newTheme: 'light' | 'dark') {
theme.value = newTheme;
}
return {
isSidebarOpen,
theme,
toggleSidebar,
setTheme,
};
});
```
## Persistence 적용 패턴
```ts
// stores/useAuthStore.ts
export const useAuthStore = defineStore('auth', () => {
const token = ref<string | null>(null);
// ... 생략
return { token };
}, {
persist: {
pick: ['token'],
},
});
```
## 스토어 네이밍 규칙
- 파일: `stores/use[Domain]Store.ts`
- 함수: `use[Domain]Store`
- defineStore ID: 소문자 도메인명 (예: `'auth'`, `'cart'`, `'ui'`)
## 주의사항
- **Option Store 대신 Setup Store 사용**: `<script setup>` 패턴과의 일관성
- **서버 데이터를 Pinia에 저장하지 않음**: `useFetch`/`useAsyncData` 영역
- **SSR hydration**: 스토어 상태는 HTML로 직렬화됨. 함수, DOM 참조, 클래스 인스턴스 등 직렬화 불가능한 값 저장 금지
- **스토어 간 순환 참조 금지**: A → B → A 형태의 의존성 제거
- **대형 모놀리식 스토어 지양**: 도메인별 분리 (auth, ui, cart 등)
- **Action 내 직접 API 호출 주의**: 프로젝트 규칙에 따라 composable에서 호출 후 결과만 저장하는 패턴 확인
- `any` 타입 사용 금지 (`rules/coding-conventions.md` 참조)