[vue:bug] 왜 mounted()가 다 실행되기 전에 화면이 먼저 렌더링 될까?
문제 상황
mounted() 메서드를 통해 DB에서 팀 정보와 팀 리그 기록을 가져온다. 이때는 서버와의 통신은 axios를 사용한다.
mounted 코드는 route 파라미터 id를 이용해 먼저 팀을 조회. 조회한 팀을 기반으로 팀의 기록을 조회한다.
<script>
export default {
...
mounted() {
console.log("mounted!");
// 팀 조회
this.axios.get("/teams/"+this.id).then(res => {
this.team = res.data;
// 팀의 대회 기록 조회
this.axios.get("/participants/search?team="+this.id).then(res => {
this.records = res.data.content;
this.records.forEach(record => {
this.axios.get("/leagues/"+record.leagueId).then(res => {
record.title = res.data.title;
})
})
})
})
},
...
}
</script>
<template>
<div class="row p-0" style="height: 800px; border-top: 1px solid #0d0d0d;">
<!-- side page -->
<div class="p-0 sub" style="width: 20%;">
<img src="@/assets/img/lck.png">
<div class="title my-3">
<h1>{{ team.shortName }}</h1>
<h3>{{ team.name }}</h3>
</div>
</div>
...
</div>
</teamplate>
조회한 팀 정보를 기반으로 아래와 같이 화면에 팀의 이니셜(team.shortName)과 팀명(team.name)을 출력한다.
실제 코드를 실행하니 화면은 렌더링 되지 않았고, mounted()가 호출되지 않아 team, records가 비어있는 것을 볼 수 있다.
원인 분석
내가 예상했던 라이프 사이클은 아래와 같다.
- mounted()를 통해 데이터 전부 호출 -> 데이터를 기반으로 화면 렌더링
그런데 mounted가 다 실행되기 전에 이미 화면이 렌더링되서 team.shortName을 호출하는 시점에 아직 team.shortName이 들어와 있지않아 NPE를 발생시켰다.
문제는 axios에 존재하는 것 같았다. axios의 메서드는 기본이 비동기 실행이 문제를 발생시키는 아닐가 추정됐다. mounted()는 axios를 비동기 실행하고, 다음 코드를 곧바로 실행한다. 즉, mounted()는 일단 서버에 get 메세지만 날리고 메서드 종료 후, 화면을 렌더링할 수 있다. 이러면 실제 팀 데이터를 받는 시점보다, 화면에서 팀 데이터를 호출하는 시점이 빠를 수도 있는 것이다.
해결
팀 데이터가 null일 경우, 화면을 렌더링 하지 않는다. vue의 v-if 속성을 통해 쉽게 설정할 수 있다.
아래와 같이 가장 큰 div 컨테이너에 div를 설정하면 team 데이터가 들어올때까지 화면은 렌더링 되지 않고, 팀 데이터가 들어오는 시점에 화면이 렌더링 된다.
<script>
export default {
...
mounted() {
console.log("mounted!");
// 팀 조회
this.axios.get("/teams/"+this.id).then(res => {
this.team = res.data;
// 팀의 대회 기록 조회
this.axios.get("/participants/search?team="+this.id).then(res => {
this.records = res.data.content;
this.records.forEach(record => {
this.axios.get("/leagues/"+record.leagueId).then(res => {
record.title = res.data.title;
})
})
})
})
},
...
}
</script>
<template>
<div v-if="team !== null" class="row p-0" style="height: 800px; border-top: 1px solid #0d0d0d;">
<!-- side page -->
<div class="p-0 sub" style="width: 20%;">
<img src="@/assets/img/lck.png">
<div class="title my-3">
<h1>{{ team.shortName }}</h1>
<h3>{{ team.name }}</h3>
</div>
</div>
...
</div>
</teamplate>
추가
mounted() 메서드 내에 비동기 처릴 위한 코드를 작성하면 v-if 를 없앨 수 있지 않을까 생각됨.