React에서 useEffect
훅 안에서 fetch 해온 데이터를 바탕으로 화면을 재구성하듯이, Vue도 마찬가지이다.
Vue에서는 mounted
라는 라이프사이클 훅을 통해 할 수 있다. 가령 다음과 같다.
<template>
<h1>{{ movieDetail.title }}</h1>
</template>
<script>
export default {
data() {
return {
movieDetail: {},
};
},
mounted() {
// 대강 데이터를 받아와 movieDetail을 갈아끼우려는 코드...
}
};
</script>
여기서 만약 API를 요청하는 코드라면 보통 async
- await
를 써주면 간편하게 처리할 수 있다.
근데 여기서 다음과 같이 mounted에 async를 바로 붙여도 상관없을까?
<template>
<h1>{{ movieDetail.title }}</h1>
</template>
<script>
export default {
data() {
return {
movieDetail: {},
};
},
async mounted() {
const nextMovieDetail = await fetch( ... )
}
};
</script>
최근 들은 데브코스 Vue 강의에서 강사님이 mounted에 곧바로 async를 붙이는 건 권장하지 않는다고 하셨다. 줄곧 왜 그런지는 단순 궁금증으로 남겨뒀다가 Vue로 과제를 구현하면서 꼭 알고 넘어가고 싶다는 생각이 들었다. 그래서 슬랙에 질문을 하였는데 멘토님과 강사님이 너무 잘 답변해주셔서 이해할 수 있었다.
가장 적절한 답은 Vue 깃헙 레포의 issues에 나와있었다.
슬랙에 질문한 것의 답변과 위 링크의 내용을 바탕으로 내가 이해한 바를 간단히 요약해보겠다.
먼저 Vue의 라이프사이클 자체는 다음과 같다. (출처 : Vue 공식 홈페이지)
여기서 세세하게 볼 필요 없이, 중요한 점은 그림에 나온 beforeCreate, created, beforeMount와 같은 라이프사이클 훅들은 동기적으로 동작한다. 근데 여기서 async를 붙여서 비동기로 동작하게 하면 어떻게 될까?
그렇다고 해서 created가 비동기로 바뀌는 건 아니다. 그 안의 await가 선언된 함수는 제대로 이행하겠지만, Vue가 created에 async가 선언되어 있다고 해서 await 함수가 다 이행될 때까지 Vue가 created에 머물러 있는게 아닌 것이다.
Forcing Vue to delay creation or mounting or any of the other lifecycle methods to wait on your network request or long-running asynchronous process will impact the user in noticeable ways. Imagine a user coming to your site and then having to wait for 4 seconds with a blank screen while the component waits for the user's spotty cell connection to finish your network request.
(링크)
어찌보면 사용자 경험을 위해 당연한 것이다. 저 created에서 만약 오랜 시간이 걸리는 비동기 함수가 실행된다면 사용자는 계속 빈 화면만 보고 있을 것이다. 따라서 created에 async를 선언해도, await로 선언된 함수가 다 이행되기 전에 그냥 beforeMount 훅으로 넘어갈 수 있다.(물론 엄청 빠르다면 마치 await 함수를 다 이행하고 넘어가는 듯이 보일 수 있지 않을까라는 생각이 든다)
즉 그냥 라이플사이클 훅에 async를 붙이는 건 오해의 여지를 줄 수 있다. created든 mounted든 거기서 멈춰서 await 함수들을 다 이행할 때까지 기다리는 것이 아니라 Vue는 그냥 동기적으로 다음 라이프사이클 훅으로 넘어가기 때문이다. 따라서 혹시나 created 안에 끝나야 하는 await 함수라면 문제가 될 수도 있겠다.
그래서 그냥 권장되는 방법은 다음과 같다.
<template>
<h1>{{ movieDetail.title }}</h1>
</template>
<script>
export default {
data() {
return {
movieDetail: {},
};
},
mounted() {
this.fetchData();
},
methods: {
async fetchData() {
const data = await fetch( ... )
this.movieDetail = data;
return data;
}
}
};
</script>
그냥 methods에 따로 빼줘서 선언해주면 된다.
결론은 created, mounted 훅에 async를 붙여도, 그 async 안의 await 함수들 동작이 다 끝나기 전에 다음 라이프사이클 훅으로 넘어갈 수도 있다. 따라서 혹여나 버그가 발생할 수도 있고, 위 내용을 모르는 다른 사람들이 봤을 때 '이 훅에서 이 await 함수들의 동작이 끝나면 다음 라이프사이클 훅으로 가겠구나'라는 오해를 줄 수 있다. 그냥 넘어가버릴 수도 있는데 말이다.
혹여나 틀린 내용이 있거나 피드백 해주실 것이 있다면, 알려주시면 감사드리겠습니다.