Nuxt 3는 데이터를 가져오는 composable로 useAsyncData와 useFetch를 제공한다. 두 코드는 모양이 거의 같아서 처음 보면 언제 어느 쪽을 써야 하는지 헷갈린다.
이 글의 처음 버전에서는 둘의 차이를 실행 시점으로 설명했다. useFetch는 created 이후에, useAsyncData는 created 이전에 돈다는 식이었다. 다시 확인해 보니 그건 Nuxt 2의 fetch 훅과 asyncData 이야기였다. 여기서는 Nuxt 3 기준으로 두 composable이 실제로 어떻게 다른지 보고, useAsyncData의 key 캐싱과 refresh를 정리한다.
실행 시점은 둘이 같다
Nuxt 2에서는 시점 차이가 맞는 설명이었다. asyncData는 페이지 컴포넌트를 불러오기 전에 실행되므로 this에 접근할 수 없다. fetch 훅은 컴포넌트 인스턴스가 만들어진 뒤에 돌기 때문에 this를 쓸 수 있다.
Nuxt 3의 useAsyncData와 useFetch는 둘 다 setup 함수(또는 플러그인, 라우트 미들웨어) 안에서 호출하는 composable이다. Vue 3에서 setup은 beforeCreate, created보다 먼저 실행되므로 둘 사이에 created 이전·이후 같은 시점 차이는 없다. 같은 자리에서 호출하면 같은 때에 돈다.
useFetch는 useAsyncData를 감싼 것이다
차이는 시점이 아니라 감싼 정도에 있다. 공식 문서는 useFetch를 useAsyncData와 $fetch를 감싼 wrapper라고 설명한다. URL과 옵션으로 key를 자동으로 만들고, 요청은 $fetch로 보낸다.
// useFetch: key를 URL과 옵션으로 자동 생성하고 $fetch로 요청한다
const { data: a } = await useFetch('http://localhost:8080/api/user/1')
// 위 한 줄을 useAsyncData로 풀어 쓰면 대략 이렇다
const { data: b } = await useAsyncData('user', () => $fetch('http://localhost:8080/api/user/1'))
URL 하나로 끝나는 요청이면 useFetch로 충분하다. 여러 API를 묶거나 응답을 가공하느라 fetcher 함수를 직접 짜야 할 때 useAsyncData를 꺼낸다. $fetch 자체는 이 글에서 다루지 않는다.
두 composable이 공통으로 해 주는 일도 있다. 공식 문서의 useAsyncData 설명을 옮기면 이렇다.
useAsyncData는 setup 함수, 플러그인 또는 라우트 미들웨어에서 직접 호출되는 컴포저블(composable)이다. 반응성을 갖는 값을 반환하며, 서버에서 받은 응답을 Nuxt payload에 담는다. 그래서 페이지가 hydrate될 때 클라이언트에서 같은 데이터를 다시 가져오지 않고 서버에서 받은 데이터를 그대로 쓴다.
서버에서 한 번 받은 데이터를 클라이언트가 다시 요청하지 않는다는 점이 이 composable들을 쓰는 이유다. 컴포넌트 안에서 fetch를 그냥 부르면 서버 렌더링 때 한 번, 클라이언트에서 한 번, 두 번 요청이 나간다.
useAsyncData의 인자와 반환값
const { data, pending, error, refresh } = await useAsyncData(key, fetcher)
인자는 두 개다.
key: 캐시된 데이터를 가져오거나 저장할 때 쓰는 고유한 키fetcher: 비동기 작업을 수행하는 함수
반환값은 네 개다.
data:fetcher함수가 반환하는 값. 아직 데이터가 로드되지 않았거나 오류가 나면null이다.pending: 데이터를 로드 중인지 알려 주는 불리언. 가져오는 동안은true, 그 외에는false다.error: 데이터를 가져오다 발생한 오류. 오류가 없으면null이다.refresh: 호출하면fetcher를 다시 실행해 데이터를 새로 가져온다. 아래에서 따로 본다.
실제로 쓰면 이런 모양이다.
import { useAsyncData } from 'nuxt3'
export default {
setup() {
const { data, pending, error, refresh } = useAsyncData('user', () =>
fetch('http://localhost:8080/api/user/1')
.then(response => response.json())
)
return { data, pending, error, refresh }
}
}
'user'라는 key로 http://localhost:8080/api/user/1에서 사용자 정보를 가져온다. Nuxt 3에서는 useAsyncData가 auto-import되므로 import 문 없이 써도 된다.
key로 캐시하면 refresh로 갱신한다
'user'는 데이터를 캐싱하는 데 쓰는 고유 식별자다. 같은 key로 비동기 데이터 요청이 여러 번 일어나면 Nuxt 3는 캐시된 데이터를 돌려주고 중복 요청을 막는다. 이 key는 Nuxt 안에서만 쓰이고 API 서버로 보내는 요청에는 실리지 않는다.
문제는 신선도다. 서버의 데이터가 바뀌어도 클라이언트의 캐시는 그대로라서 클라이언트는 최신 데이터를 보지 못한다. 캐시를 쓰는 대가로 갱신 시점은 직접 정해야 한다. 그때 쓰는 것이 refresh로, 호출하면 fetcher를 다시 실행해 캐시를 갱신하고 최신 데이터를 가져온다.
import { useAsyncData } from 'nuxt3'
export default {
setup() {
const { data, refresh } = useAsyncData('user', () =>
fetch('http://localhost:8080/api/user/1')
.then(response => response.json())
)
// 버튼을 누르면 fetcher를 다시 실행한다
const refreshData = () => {
refresh()
}
return { data, refreshData }
},
template: `
<div>
<div v-if="data">
<p>{{ data.name }}</p>
<p>{{ data.email }}</p>
</div>
<button @click="refreshData">Refresh Data</button>
</div>
`
}
사용자 정보를 조회해 이름과 이메일을 화면에 보여 준다. Refresh Data 버튼을 누르면 refreshData가 호출되고 서버에서 최신 데이터를 다시 받아 화면에 반영한다.