Pattern chuẩn để fetch async data trong Vue 3 Composition API là dùng onMounted với try/catch/finally, hoặc trích xuất thành composable tái sử dụng.
vue
<script setup>
import { ref, onMounted } from 'vue'
const users = ref([])
const loading = ref(true)
const error = ref(null)
// Pattern 1: onMounted (phổ biến nhất)
onMounted(async () => {
try {
const res = await fetch('/api/users')
users.value = await res.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
})
// Pattern 2: Composable reusable
// useFetch.ts — tái sử dụng ở nhiều components
</script>
<template>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<ul v-else>
<li v-for="u in users" :key="u.id">{{ u.name }}</li>
</ul>
</template>Lưu ý: không gọi async setup() trực tiếp mà không có <Suspense> bọc ngoài — dùng onMounted hoặc composable thay.
Với Nuxt: dùng useFetch() / useAsyncData() để SSR-compatible.
The standard pattern for fetching async data in Vue 3 Composition API is to use onMounted with try/catch/finally, or extract it into a reusable composable.
vue
<script setup>
import { ref, onMounted } from 'vue'
const data = ref(null)
const loading = ref(true)
const error = ref(null)
onMounted(async () => {
try {
const res = await fetch('/api/data')
data.value = await res.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
})
</script>
<template>
<div v-if="loading">Loading...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else>{{ data }}</div>
</template>Pitfall: do not use async setup() without a <Suspense> wrapper — use onMounted or a composable instead.
In Nuxt: use useFetch() / useAsyncData() for SSR compatibility.