Vue로 회사 프로젝트를 작업하는 도중,
Vue도 Suspense 기능을 지원하는 것을 보고 적용해 보았음.
Suspense 기능이 친숙한건 아니고, 저번에 react로 적용해 보았을 때 loading 컴포넌트를 분리하기 편리하겠다 생각했기 때문에 이번에도 적용해 보기로 하였다.
https://vuejs.org/guide/built-ins/suspense.html#loading-state
간단하게, 서버에서 유저 리스트를 불러와서 table 형식으로 만든다고 생각해 보자.
suspense는 요청이 완료될 때까지 로딩을 띄워주고, 성공하면 테이블, 실패하면 에러 컴포넌트를 출력해 준다.
// parent.vue
<script setup lang="ts">
import { defineComponent, ref } from 'vue'
import AsyncAdminMemberTable from "@/components/admin/AsyncAdminMemberTable.vue"
import AdminError from '@/components/admin/adminError.vue'
import LoadingSpinner from '@/components/LoadingSpinner.vue'
import { onErrorCaptured } from 'vue' // 요기서 suspense의 에러 여부를 받는다.
const hasError = ref<boolean>(false)
onErrorCaptured((e: Error) => {
console.error(e)
hasError.value = true
return false
})
defineComponent({
name: 'AdminMain'
})
</script>
<template>
<div class="flex w-full">
<AdminError v-if="hasError" /> // error일 경우 Suspense가 아니라 에러를 표시함
<Suspense v-else> // error가 아니라면, 실제 원하는 컴포넌트를 출력
<AsyncMemberTable.vue />
<template #fallback> // 로딩을 위한 컴포넌트
<LoadingSpinner />
</template>
</Suspense>
</div>
</template>
// AsyncMemberTable.vue
<script setup lang="ts">
import { defineComponent } from 'vue'
function getTestData() {
return new Promise((resolve) =>
resolve({
data: [
{
name: 'Bill',
email: 'billcollins323@hotmail.com',
profilePic:
'https://xf-assets.pokecharms.com/data/attachment-files/2015/10/236934_Squritle_Picture.png'
},
{
name: 'Jihyeon',
email: 'jihyeonjeong@gmail.com',
profilePic:
'https://xf-assets.pokecharms.com/data/attachment-files/2015/10/236933_Charmander_Picture.png'
},
{
name: 'pacho',
email: 'josemunez@naver.com',
profilePic:
'https://xf-assets.pokecharms.com/data/attachment-files/2015/10/236932_Bulbasaur_Picture.png'
}
]
})
)
}
const getMembersAsAdmin = async function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
getTestData()
.then((res: any) => {
resolve(res)
})
.catch((e) => {
reject(new Error(e))
})
}, 1000)
})
}
const data = (await getMembersAsAdmin()) as any
const members = data.data
defineComponent({
name: 'AsyncAdminMemberTable'
})
</script>
<template>
<div class="w-full flex flex-col gap-y-4">
<div v-for="{profilePic, name, email}, i in members" :key="i" class="flex flex-col gap-y-2 items-center">
<img alt="randompokemonpic" :src="profilePic" class="w-32 h-32" />
<span>{{name}}</span>
<span>{{email}}</span>
</div>
</div>
</template>
와 같은 식으로 만들어 볼 수 있었다.
성공한 화면
실패한 화면
이를 통해서 loading을 state나 ref과 같이 컨트롤하지 않아도 된다는 것은 분명 매력적인 부분이라고 생각한다.