티스토리 뷰

vue로 프론트엔드 개발을 하던 도중, 글로벌 범위로 사용되는 Footer 에서 router안에 들어있는 특정 DOM에 접근해야 할 일이 생겼다(정확히 말하자면 DOM안에 특정 class가 존재하는지 유뮤를 확인해야 했다). 이런 상황에서는 늘 그랬듯이 Footer 스크립트 내에서 document.querySelector('.특정클래스')함수를 호출하여 해당 DOM에 접근하려고 시도했는데, 결과값을 null로 반환되었다.
파일 구조는 아래와 같이 구성되어있다.

//App.vue
<template>
  <NavBar />
  <router-view />
  <Footer />
</template>

<script>
import NavBar from "./components/NavBar.vue";
import Footer from "./components/Footer.vue";
export default {
  components: { NavBar, Footer },
};
</script>
// NavBar.vue
<template>
  <nav>
    <router-link :to="{ name: 'MainPage' }">Main</router-link>
  </nav>
</template>
// Home.vue
<template>
  <h1 class="home">This is home</h1>
</template>
// Footer.vue
<template>
  <footer>this is footer</footer>
</template>

<script>
import { onMounted } from "vue";
export default {
  setup() {
    onMounted(() => {
      const navEl = document.querySelector("nav");
      const homeEl = document.querySelector(".home");

      console.log(navEl); // <nav...> - found
      console.log(homeEl); // null - not found
    });
  },
};
</script>

뭔가 이상하다고 생각이 들었다. router내부에 있는 DOM요소(위 코드상에서는 'home'이라는 클래스)에 접근하는 건데 왜 못 찾는 걸까? 혹시나 하는 마음에 똑같이 글로벌로 사용되는 nav를 찾아보았을 때는 역시 잘 찾고 있었다. 내 경험상 vue에서 특정 DOM을 찾지 못하는 문제에 부딪혔을 때는 일단 setTimeout으로 딜레이를 걸어서 찾으면 해결되는 경우가 많았다. 그래서 일단 아래와 같이 코드를 수정해보았다.

// Footer.vue
<script>
export default {
  setup() {
    onMounted(() => {
      setTimeout(()=>{
      const navEl = document.querySelector("nav");
      const homeEl = document.querySelector(".home");

      console.log(navEl); // <nav...> - found
      console.log(homeEl); // <h1...> - found
      },1000)
    });
  },
};
</script>

아니나 다를까, 시간을 두고 DOM을 찾았더니 성공하였다. 우선 가장 무식한(비효율적인) 방법으로는 문제를 해결하기는 했다.


이런 문제 상황의 원인은 바로 App.vue에서 routermount 될 때까지 미세하게나마 시간이 소요되기 때문이었다. 반면에 Footer.vuerouter 내부에 있지 않고 App.vue에 독립적으로 존재하기 때문에 mount되는 시간이 훨씬 짧다. 따라서 Footer.vue에서의 onMounted()가 호출되는 시점에는 아직 router내부의 Home.vue의 DOM이 mount되기 이전 시점이었기 때문에 DOM에 접근할 수 없었고, 같은 레벨에 있는 NavBar.vue에는 접근이 가능했던 것이다.
개발할 때는 워낙 빠릿하게 작동해서 눈치채기는 어렵지만 사실 router가 mount되기까지의 시간딜레는 결코 무시할 수 없는 수준으로 꽤 긴 시간이다. 그렇다면 앞으로도 계속 setTimeout으로 강제 딜레이를 넣어줘야 하는 걸까? 당연히 아니다.vue-router에서는 이런 문제가 발생할 줄 알고 미리 router.isReady()라는 비동기 함수를 만들어 놓았다. 문제를 해결한 코드를 먼저 보자.

// Footer.vue
<script>
import { onMounted } from "vue";
import router from "@/router";
export default {
  setup() {
    onMounted(async () => {
      await router.isReady();
      const navEl = document.querySelector("nav");
      const homeEl = document.querySelector(".home");

      console.log(navEl); // <nav...> - found
      console.log(homeEl); // <h1...> - found
    });
  },
};

적용은 간단하다. querySelector()로 찾기 이전 라인에 await router.isReady()를 적어주기만 하면 된다. 콘솔로 찍히는데 약간의 시간 딜레이가 있기는 하지만, setTimeout으로 하드코딩하는 것보단 훨씬 낫다.

댓글