Vue 프로젝트에서 비동기 API를 호출할 때, 대부분의 데이터는 상태로 저장되고 여러 컴포넌트에서 공유됩니다.
이때 Pinia를 통해 상태를 관리하면서 동시에 API 호출과 데이터 가공까지 맡긴다면 훨씬 깔끔하고 유지보수하기 쉬운 구조를 만들 수 있습니다.
이번 포스팅에서는 Pinia 스토어에서 비동기 API를 안전하게 호출하고 상태를 관리하는 방법을 아래와 같은 흐름으로 정리합니다:
✅ 목표 구조
- 상태: data, isLoading, error 분리
- 액션: Axios + DTO 처리
- 컴포넌트에서는 store만 불러 사용
1. 📦 기본 구조 예시: 유저 리스트
// src/stores/userStore.ts
import { defineStore } from 'pinia'
import axios from 'axios'
interface User {
id: number
name: string
email: string
}
export const useUserStore = defineStore('user', {
state: () => ({
users: [] as User[],
isLoading: false,
error: '' as string | null,
}),
actions: {
async fetchUsers() {
this.isLoading = true
this.error = null
try {
const res = await axios.get<User[]>('/api/users')
this.users = res.data
} catch (e: any) {
this.error = e?.message ?? '알 수 없는 오류'
} finally {
this.isLoading = false
}
},
},
})
💡 상태를 users, isLoading, error로 분리하면 컴포넌트에서 UI 조건 분기 처리하기 쉽습니다.
2. 🧱 DTO 패턴 함께 사용하기 (선택적)
// src/dto/UserDTO.ts
export class UserDTO {
id: number
name: string
email: string
constructor(data: any) {
this.id = data.id ?? -1
this.name = data.name ?? '이름없음'
this.email = data.email ?? ''
}
get displayName() {
return this.name.toUpperCase()
}
}
그리고 store에서는 아래와 같이 DTO로 가공:
import { UserDTO } from '@/dto/UserDTO'
const res = await axios.get('/api/users')
this.users = res.data.map((u: any) => new UserDTO(u))
3. 🧩 컴포넌트에서 사용하는 방식
<script setup lang="ts">
import { onMounted } from 'vue'
import { useUserStore } from '@/stores/userStore'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
const { users, isLoading, error } = storeToRefs(userStore)
onMounted(() => {
userStore.fetchUsers()
})
</script>
<template>
<div>
<p v-if="isLoading">로딩 중...</p>
<p v-else-if="error">오류: {{ error }}</p>
<ul v-else>
<li v-for="user in users" :key="user.id">
{{ user.displayName ?? user.name }}
</li>
</ul>
</div>
</template>
✅ 실무에서 이 패턴을 써야 하는 이유
| 이유 | 설명 |
| 💡 책임 분리 | API 호출 로직이 컴포넌트 밖(Pinia)에 있어서 재사용성과 가독성 향상 |
| 🔄 로딩/에러 상태 분리 | UI 제어가 쉬움 (v-if 조건 처리 용이) |
| ✅ 타입 안전성 | DTO 또는 Axios 제네릭으로 타입 추론 가능 |
| 👥 여러 컴포넌트 공유 | 하나의 store로 상태를 전역 관리 |
🏁 마무리
Pinia 상태에서 비동기 호출을 직접 다루는 것은 실무에서 매우 흔한 패턴입니다.
아래와 같은 기능들을 앞으로 확장할 수 있습니다:
- 페이지네이션 (page, limit 상태 추가)
- 검색 필터 (keyword, sort 등 상태로 관리)
- 캐싱 (최초 호출만 요청 등)
✅ 컴포넌트는 최대한 UI 역할만 담당하고, 데이터 요청은 Pinia 스토어에 위임하세요. 유지보수가 쉬운 Vue 아키텍처가 완성됩니다!
'FrontEND > vue' 카테고리의 다른 글
| JWT 기반 인증을 Vue + Axios + TypeScript로 구현하는 방법 (0) | 2025.05.07 |
|---|---|
| 배열, 중첩 객체, 에러 응답을 안전하게 처리하는 TypeScript + DTO 패턴 가이드 (0) | 2025.04.30 |
| TypeScript에서 안전하게 API 응답 객체 처리 및 Axios + DTO 구조로 타입 안정성 높이기 (0) | 2025.04.29 |
| TypeScript에서 객체를 안전하게 초기화하는 방법 (0) | 2025.04.28 |
| TypeScript에서 옵셔널 파라미터(?), 옵셔널 체이닝(?.), ??, || 정리 (0) | 2025.04.27 |