티스토리 뷰
Suspense
<Suspense> 는 비동기 컴포넌트가 로드되는 동안 fallback UI를 보여주는 Vue의 내장 컴포넌트
아직 실험적 기능이라 프로덕션 환경에서는 신중한 사용 필요
기존 방식
- 매번 loading, error 상태 직접 관리
- 중복된 코드
<template>
<div>
<div v-if="loading">로딩 중...</div>
<div v-else-if="error">에러 발생: {{ error }}</div>
<div v-else>
<UserProfile :data="userData" />
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const loading = ref(true)
const error = ref(null)
const userData = ref(null)
onMounted(async () => {
try {
const response = await fetch('/api/user')
userData.value = await response.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
})
</script>
Suspense를 사용한 방식
- 로딩 상태 자동 관리
<template>
<Suspense>
<template #default>
<UserProfile />
</template>
<template #fallback>
<div>로딩 중...</div>
</template>
</Suspense>
</template>
<script setup>
// 로딩 상태 관리 불필요
</script>
Vue3 에서의 사용
<!-- UserProfile.vue -->
<template>
<div class="profile">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
</template>
<script setup>
// async를 사용하면 Suspense가 자동으로 대기
const response = await fetch('/api/user')
const user = await response.json()
</script>
<!-- App.vue -->
<template>
<Suspense>
<!-- 비동기 컴포넌트 -->
<template #default>
<UserProfile />
</template>
<!-- 로딩 중 표시할 UI -->
<template #fallback>
<div class="loading">
<span class="spinner"></span>
사용자 정보를 불러오는 중...
</div>
</template>
</Suspense>
</template>
<script setup>
import UserProfile from './components/UserProfile.vue'
</script>
<style scoped>
.loading {
display: flex;
align-items: center;
gap: 10px;
padding: 20px;
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
</style>
여러 비동기 컴포넌트 처리 가능
<template>
<Suspense>
<template #default>
<div class="dashboard">
<!-- 모든 컴포넌트가 로드될 때까지 대기 -->
<UserProfile />
<Statistics />
<RecentActivity />
</div>
</template>
<template #fallback>
<DashboardSkeleton />
</template>
</Suspense>
</template>
<script setup>
// 각 컴포넌트가 async setup을 가짐
import UserProfile from './UserProfile.vue'
import Statistics from './Statistics.vue'
import RecentActivity from './RecentActivity.vue'
import DashboardSkeleton from './DashboardSkeleton.vue'
</script>
Nuxt3에서의 사용
<!-- pages/users/[id].vue -->
<template>
<div>
<h1>사용자 프로필</h1>
<Suspense>
<template #default>
<UserDetail :userId="route.params.id" />
</template>
<template #fallback>
<UserDetailSkeleton />
</template>
</Suspense>
</div>
</template>
<script setup>
const route = useRoute()
</script>
<!-- components/UserDetail.vue -->
<template>
<div class="user-detail">
<img :src="user.avatar" :alt="user.name" />
<h2>{{ user.name }}</h2>
<p>{{ user.bio }}</p>
</div>
</template>
<script setup>
const props = defineProps<{ userId: string }>()
// Nuxt의 useFetch는 자동으로 Suspense를 지원
const { data: user } = await useFetch(`/api/users/${props.userId}`)
</script>'Frontend' 카테고리의 다른 글
| CSS Layout 정리 (0) | 2026.02.23 |
|---|---|
| React - useMemo & useCallback (0) | 2026.02.11 |
| Vue3, Nuxt3 - Image Lazy Loading (0) | 2026.02.06 |
| Vue3 - vue3-virtual-scroller (0) | 2026.02.03 |
| Nuxt3 - Google Tag Manager(GTM) 연동 (0) | 2026.01.20 |