Nuxt의 Data Fetching

비동기 데이터 호출

Nuxt.js

Nuxt는 Vue.js의 서버사이드 렌더링을 지원하기 위한 프레임워크로써, Vue.js SPA에서 데이터를 호출하는 방식과 다르게 비동기 데이터 호출을 위한 Hook이 따로 존재합니다. 이번 포스트에서는 그 Hook인 fetchasyncData Hook에 대해 살펴보겠습니다.

fetch & asyncData

일단 공식문서의 간략한 설명을 번역하면 아래와 같다.

  • fetch hook (Nuxt 2.12+)은 모든 컴포넌트에서 사용할 수 있고, (client-side 렌더링 중) 렌더링이 진행중인 상태와 에러에 대한 참조(shortcuts)를 제공합니다.
  • asyncData은 오직 page 컴포넌트에서만 사용할 수 있습니다. fetch와 다르게 client-side 렌더링 중 로딩 placeholder를 표시할 수 없습니다: 대신에, 이 hook이 완료될때까지 route navigation을 막으며 실패했을 경우 페이지에 에러를 표시합니다.

아직 두개의 hook에 대해 너무나도 간략한 설명이기에 더 자세히 해보겠습니다.

fetch

fetch는 컴포넌트 인스턴스가 생성된 후 server-side 렌더링 동안 또는 client-side에서 네비게이션 되는 동안 호출되는 hook으로, 비동기 데이터 호출이 완료되었을때 (async/await를 사용) promise를 반환합니다.

  • 초기 페이지가 렌더링 될때 서버사이드에서 호출
  • 컴포넌트가 mounted 된 후 클라이언트에서 호출

fetch hook은 컴포넌트 레벨에서 아래의 property와 함께 $fetchState와 함께 노출됩니다.

  • pendingBoolean 값으로 client-side 에서 fetch hook이 호출되었을때 placeholder를 표시하도록 해줍니다.
  • errornull 또는 fetch hook이 반환한 Error입니다.
  • timestamp는 최신 fetch의 timestamp로써, keep-alive으로 caching 하는 것에 유용합니다.

Nuxt에 의해 fetch가 호출되는 것이외에도 컴포넌트에서 수동으로 this.$fetch()를 호출해 fetch를 사용할 수도 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<p v-if="$fetchState.pending">Fetching mountains...</p>
<p v-else-if="$fetchState.error">An error occurred :(</p>
<div v-else>
<h1>Nuxt Mountains</h1>
<ul>
<li v-for="mountain of mountains">{{ mountain.title }}</li>
</ul>
<button @click="$fetch">Refresh</button>
</div>
</template>

<script>
export default {
data() {
return {
mountains: []
}
},
async fetch() {
this.mountains = await fetch(
'https://api.nuxtjs.dev/mountains'
).then(res => res.json())
}
}
</script>

Option

  • fetchOnServer: Boolean or Function (기본값: true)이며 server-rendering 중에 fetch()를 호출할지 설정할 수 있습니다.
  • fetchDelay: Integer (기본값: 200)이며 millisecond 단위로 최소 실행시간을 설정할 수 있습니다.

fetchOnServerfalse 또는 false를 반환할 경우, fetch는 오직 client-side에서만 호출될 것이며 $fetchState.pending은 컴포넌트가 server-rendering일때 true를 반환할 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default {
data() {
return {
posts: []
}
},
async fetch() {
this.posts = await fetch('https://api.nuxtjs.dev/posts').then(res =>
res.json()
)
},
// call fetch only on client-side
fetchOnServer: false
}

querystring 변경 감지하기

fetch hook은 기본적으로 querystring 변경시 호출되지 않는다. 이를 위해 $route.query를 관찰해 $fetch를 호출해야합니다.

1
2
3
4
5
6
7
8
export default {
watch: {
'$route.query': '$fetch'
},
async fetch() {
// Called also on query changes
}
}

캐싱

<nuxt/><nuxt-child/> 컴포넌트에서 이미 방문했던 페이지의 fetch 호출을 절약하기 위해 keep-alive 디렉티브를 사용할 수 있습니다.

1
2
3
<template>
<nuxt keep-alive />
</template>

또한, <nuxt> 컴포넌트에 keep-alive-props prop을 전달함으로써 <keep-alive>에 전달될 props를 명시할 수 있습니다.

1
<nuxt keep-alive :keep-alive-props="{ max: 10 }" />

메모리상 오직 10개 페이지 컴포넌트만 유지할 수 있습니다.

activated hook 사용하기

Nuxt는 (ssr을 포함한) 가장 최신의 fetch 호출에 대한 this.$fetchstate.timestamp를 직접 채웁니다. fetch에 30초 캐시를 추가하기 위해 activated hook을 혼합해 fetch property를 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template> ... </template>

<script>
export default {
data() {
return {
posts: []
}
},
activated() {
// Call fetch again if last fetch more than 30 sec ago
if (this.$fetchState.timestamp <= Date.now() - 30000) {
this.$fetch()
}
},
async fetch() {
this.posts = await fetch('https://api.nuxtjs.dev/posts').then(res =>
res.json()
)
}
}
</script>

마지막 fetch 호출이 30초 전이었다면, 동일 페이지 내 navigation은 fetch를 호출하지 않을 것입니다.

Async Data

asyncData는 오직 pages 안에서만 사용가능하고 이 hook 안에서는 this 객체에 접근할 수 없습니다.

asyncData는 universal nuxt app의 비동기 데이터 호출을 위한 또 다른 hook으로, fetch와 다르게 컴포넌트 인스턴스에서 비동기 state를 저장하기 위한 property를 설정해줘야 합니다(또는 Vuex actions의 dispatch). asyncData는 간단하게 컴포넌트의 지역 state에 반환된 값을 병합(merge)합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.description }}</p>
</div>
</template>

<script>
export default {
async asyncData({ params, $http }) {
const post = await $http.$get(`https://api.nuxtjs.dev/posts/${params.id}`)
return { post }
}
}
</script>

fetch와 다르게 route transition 동안 asyncData가 완료되고 promise를 반환합니다. 이것이 의미하는 바는 client-side transitions 동안에는 로딩 placeholder(loading placeholder)는 보여지지 않는다는 것입니다 (비록 유저에게 진행상태를 알려주기 위해 진행바를 사용할 수 있을지라도). 대신에 Nuxt는 다음 페이지로 navigation 되기 전 완료되거나 에러 페이지를 보여줄때까지 asyncData hook을 기다립니다.

asyncData hook은 오직 page-level 컴포넌트에서 사용할 수 있습니다. fetch와 다르게 asyncData hook은 컴포넌트 인스턴스(this)에 접근할 수 없지만, 인자(argument)로써 context를 받으며 데이터를 호출하기 위해 사용할 있고, Nuxt.js는 자동으로 컴포넌트의 data와 반환된 객체를 병합합니다.

컴포넌트에서 Async data

컴포넌트들은 asyncData 메서드를 가지고 있지 않기때문에 컴포넌트 내에서 server-side 비동기 데이터 호출을 할 수 없습니다. 이러한 제약사항에 관해서 3개의 옵션이 있습니다.

  1. 버전 2.12+ 또는 최신 버전의 Nuxt에서 new fetch hook 사용하기
  2. mounted hook에서 API 호출 만들고, 로드 되었을때 data property를 설정합니다. 부작용: server side rendering에서 작동하지 않습니다.
  3. page 컴포넌트의 asyncData 메서드에서 API 호출 만들고 sub 컴포넌트에 props로 data 전달해줍니다. Server rendering은 잘 작동합니다. 부작용: 페이지의 asyncData는 다른 컴포넌트에 대한 data를 불러오기 때문에 덜 합리적일 수도 있습니다.

querystring 변경 감지하기

asyncData 메서드는 기본적으로 querystring의 변경시 호출되지 않습니다. 만약 호출을 원한다면, 예를 들어 pagination 컴포넌트를 개발할때, page 컴포넌트의 watchQuery property에서 감지할 수 있는 매개변수를 설정할 수 있습니다.

(공식 홈페이지 The watchQuery Property 챕터 참조)

요약

지금부터는 공식 홈페이지에서 fetchasyncData에 대한 spec에 대해 알아봤으나 난해한 개념을 보충하기 위한 몇 개 사이트의 내용을 요약해보겠습니다.

즉, 아래와 같이 목적을 구분할 수 있다.

  • asyncData는 컴포넌트가 랜더링 되기전에 컴포넌트 데이터를 구성하는 것에 목적
  • fetch는 컴포넌트가 랜더링 되기전에 비동기 로직을 호출하는 것에 목적

Difference between Asyncdata vs Fetch

출처: https://stackoverflow.com/questions/49251437/difference-between-asyncdata-vs-fetch

  • asyncData는 컴포넌트 레벨에서 설정 가능하며 Vuex store에서 접근 가능합니다.
  • fetch는 컴포넌트 레벨에서 설정할 수 없으며 Vuex store에서 접근할 수 없습니다.
  • asyncDatafetch 둘다 server-side의 초기 렌더링에서 호출됩니다.
  • 초기 로딩 이후에, asyncDatafetch는 page의 route가 변경되었을때 호출됩니다.
  1. 만약 Nuxt 어플리케이션에서,
  • Vuex store를 (데이터) 중앙 저장소로 이용하고,
  • 어플리케이션 전체에서 Vuex store로 데이터를 접근할 경우

fetch를 사용

  1. 만약 Nuxt 어플리케이션에서,
  • Vuex store를 (데이터) 중앙 저장소로 이용하고,
  • 컴포넌트 레벨에서 설정한 옵션을 가지고 있고,
  • 특정 route에서 가져온 data가 오직 1개의 컴포넌트에서 사용될 경우,
  • Vuex store 또는 컴포넌트 레벨에 대한 권한을 가질수 있는 유연성이 필요한 경우,

asyncData 사용

기타 블로그

fetch

  • 컴포넌트를 로드하기 전에 호출
  • 모든 컴포넌트 에서 사용 가능
  • Nuxt.js는 컴포넌트가 렌더링되기 전 fetch promise가 종료될 때까지 대기함
  • fetch는 Vuex store의 data를 접근해 사용할때 사용
  • 첫 번째 인자로 context를 받으며 storeparams를 사용할 수 있음
  • 서버 사이드 렌더링을 위해 서버에서 화면을 구성할 때 컴포넌트가 생성되고 나서 실행됨
  • 브라우저에서 URL 주소를 변경해서 페이지를 이동할 때

asyncData

  • 컴포넌트를 로드하기 전에 호출

  • page 컴포넌트 에서만 사용 가능

  • asyncData로 생성한 데이터는 data로 생성한 데이터와 머지되어 사용하는 입장에서는 asyncData와 data는 차이가 없다. 단지 초기화시키는 데이터가 동기적으로 실행되는지 비동기적으로 실행되는지에 대한 차이가 있을 뿐이다.

  • 첫 번째 인자로 context를 받으며 url의 paramsquery를 사용할 수 있음

  • redirect나 error를 활용하여 원하는 페이지로 리다이렉트 시키거나 에러 페이지를 띄워줄 수 있음

  • 만일 vuex가 설정되었다면, store를 사용 가능

asyncData는 컴포넌트를 초기화 하기 전에 실행되기 때문에 메서드 내부에서는 this를 통해 컴포넌트 인스턴스에 접근할 수 없다.


참조