Vue의 라이프사이클 훅에서 비동기 처리하기

신승준·2022년 12월 17일
3
post-thumbnail

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 함수들의 동작이 끝나면 다음 라이프사이클 훅으로 가겠구나'라는 오해를 줄 수 있다. 그냥 넘어가버릴 수도 있는데 말이다.




혹여나 틀린 내용이 있거나 피드백 해주실 것이 있다면, 알려주시면 감사드리겠습니다.

profile
메타몽 닮음 :) email: alohajune22@gmail.com

0개의 댓글