Store 설계 규칙
13.2.1. Store 분할 기준
Store는 도메인 단위로 분할합니다. 하나의 Store는 하나의 도메인만 담당해야 합니다.
- 1 Store = 1 도메인 원칙을 준수합니다.
- 도메인은 비즈니스 엔티티 또는 기능 영역 기준으로 정의합니다.
- 하나의 Store가 복수 도메인을 관리하는 것은 금지합니다.
| Store | 담당 도메인 | 포함 상태 예시 |
|---|---|---|
useUserStore | 사용자 인증/프로필 | 로그인 상태, 사용자 정보, 토큰 |
useProductStore | 상품 | 상품 목록, 상품 상세, 카테고리 |
useCartStore | 장바구니 | 장바구니 항목, 수량, 합계 |
useNotificationStore | 알림 | 알림 목록, 읽음 상태 |
useThemeStore | 테마/UI 설정 | 다크모드, 언어, 레이아웃 |
13.2.2. 글로벌 vs 로컬 상태 판단
모든 상태를 Store에 배치하지 않습니다. 다음 판단 기준에 따라 관리 방식을 결정해야 합니다.
| 판단 기준 | 관리 방식 | 예시 |
|---|---|---|
| 여러 컴포넌트가 공유하는 상태 | Pinia Store | 인증 정보, 테마 설정, 권한 |
| 단일 컴포넌트에서만 사용하는 상태 | 컴포넌트 로컬 ref | 폼 입력값, 모달 표시 여부, 토글 |
| 서버에서 가져오는 비동기 데이터 | Composable (useFetch 등) | API 응답 데이터, 페이지네이션 |
| 여러 페이지에 걸친 임시 상태 | Pinia Store | 다단계 폼 위저드의 진행 데이터 |
| URL로 표현 가능한 상태 | Router (query/params) | 검색 필터, 정렬 조건, 페이지 번호 |
- 판단이 모호한 경우 로컬 상태로 시작하고, 공유 필요성이 확인된 시점에 Store로 승격합니다.
13.2.3. Store 간 의존성
Store 간 의존이 필요한 경우 action 내부에서 다른 Store를 호출합니다.
- Store 최상위 스코프에서 다른 Store를 호출하지 않습니다.
- 순환 의존은 금지합니다. A -> B -> A 형태의 참조가 발생하면 공통 Store를 분리합니다.
- 의존 방향은 상위 도메인에서 하위 도메인으로 단방향을 유지합니다.
typescript
// src/stores/useCartStore.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { useUserStore } from './useUserStore'
import type { CartItem } from '@/types/cart'
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const totalPrice = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)
// action 내부에서 다른 Store를 호출합니다.
async function checkout() {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
throw new Error('로그인이 필요합니다.')
}
await submitOrder(items.value, userStore.user!.id)
items.value = []
}
return { items, totalPrice, checkout }
})useUserStore()를checkout()action 내부에서 호출하는 것이 올바른 패턴입니다.- Store 정의 최상위에서
const userStore = useUserStore()를 선언하는 것은 금지합니다.
13.2.4. Store 네이밍
Store의 네이밍은 다음 규칙을 따릅니다.
- 함수명:
use{Domain}Store패턴을 사용합니다. (예:useAuthStore,useProductStore) defineStoreID: 도메인명을 소문자 케밥 표기로 사용합니다.- 파일명: 함수명과 동일하게
use{Domain}Store.ts로 작성합니다.
| 함수명 | defineStore ID | 파일명 |
|---|---|---|
useAuthStore | 'auth' | useAuthStore.ts |
useProductStore | 'product' | useProductStore.ts |
useOrderHistoryStore | 'order-history' | useOrderHistoryStore.ts |
typescript
// 파일명: src/stores/useAuthStore.ts
// defineStore ID: 'auth'
// 함수명: useAuthStore
export const useAuthStore = defineStore('auth', () => {
// ...
})- ID는 앱 전체에서 고유해야 합니다. 중복 ID는 런타임 오류를 발생시킵니다.
- 복합 도메인의 ID는 케밥 케이스를 사용합니다. (예:
'order-history','user-preference')