본문 바로가기
FrontEnd/Vue.js

[Vue.js] SPA로 동작하는 Vue에서 DOMContentLoaded

by 푸고배 2022. 11. 7.

SPA 환경에서의 DOMContentLoaded 동작

SPA 환경에서는 아래와 같은 DOMContentLoaded 시점에  IntersectionObserver를 추가해주는 로직을 적용하는 과정이 의도한 것과 같이 동작하지 않는다.

document.addEventListener("DOMContentLoaded", function() {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

  if ("IntersectionObserver" in window) {
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          lazyImage.classList.remove("lazy");
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });

    lazyImages.forEach(function(lazyImage) {
      lazyImageObserver.observe(lazyImage);
    });
  } else {
    // Possibly fall back to event handlers here
  }
});

DOMContentLoaded는 초기 HTML 문서를 완전히 불러오고 분석했을 때 발생하는 이벤트이다. 모든 리소스가 다운된 다음 호출되는 load와는 달리, DOMContentLoaded는 StyleSheet, Image, 하위 프레임과 같은 리소스 로딩을 기다리지 않고 DOM트리가 완성되는 순간 즉시 호출된다. 

 

SPA는 기본적으로 웹 애플리케이션에 필요한 모든 정적 리소스를 최초 접근 시 단 한번만 다운로드 한다. 이후 새로운 페이지 요청 시, 페이지 갱신에 필요한 데이터만을 JSON으로 전달받아 페이지를 갱신하므로 전체적인 트래픽을 감소시킬 수 있고, 전체 페이지를 다시 렌더링하지 않고 변경되는 부분만을 갱신하므로 새로고침이 발생하지 않아 네이티브 앱과 유사한 사용자 경험을 제공할 수 있다.

 

해결 방법

그렇다면 SPA로 동작하는 Vue와 Nuxt 환경에서는 위와 같은 코드를 어떻게 적용할 수 있을까?

라이프사이클(mounted) 훅 이용하기

export default {
data() {
	return {
		...
	}
},
mounted () {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

  if ("IntersectionObserver" in window) {
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          lazyImage.classList.remove("lazy");
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });

    lazyImages.forEach(function(lazyImage) {
      lazyImageObserver.observe(lazyImage);
    });
  } else {
    // Possibly fall back to event handlers here
  }
}

mounted 라이프사이클 훅은 인스턴스가 마운트된 후 호출되며, 여기서 Vue.createApp({}).mount()로 전달된 엘리먼트는 새로 생성된 vm.$el로 대체된다. 루트 인스턴스가 문서 내의 엘리먼트에 마운트되어 있으면, mounted가 호출될 때 vm.$el도 문서에 포함(in-document)된다. 

mounted는 모든 자식 컴포넌트가 마운트되었음을 보장하지 않으며, 전체 화면내용이 렌더링될 때까지 기다리려면, mounted 내에서 vm.$nextTick를 사용한다.

 

mounted를 이용한 방법은 해당 페이지마다 선언해줘야하는 번거로움이 있다.

DOMContentLoaded과 유사하게, 모든 페이지들이 호출되고 렌더링되는 시점마다 해당 로직을 수행해야한다면 어떻게 해결해야할까

 

Route가 변경되는 시점 이용

watch: {
    $route () {
      setTimeout(() => {
	      // ...
      }, 50);
      // react to route changes...
    },
  },

Nuxt를 사용 중이라면 App.vue, Default.vue와 같은 기본 레이아웃 컴포넌트에 watch 속성을 사용하여 route 값이 변경될 때마다 해당 로직을 수행하도록 수행해줄 수 있다. 다만, route가 변경되는 시점에는 아직 DOM이 업데이트된 상태가 아니므로 setTimeout과 같은 지연함수를 이용해 일정시간 딜레이가 필요하다.

 

하지만, 이 방법은 렌더링 시점을 보장하지 않으므로 로딩이 느린 상황에서는 정확성이 떨어진다.

 

Vue.mixin을 이용

믹스인(Mixins)은 여러 컴포넌트 간에 공통으로 사용하고 있는 로직, 기능들을 재사용하는 방법이다. 믹스인에 정의할 수 있는 재사용 로직은 data, methods, 라이프사이클 훅(created, mounted ...) 등과 같은 컴포넌트 옵션이다. 

import Vue from 'vue';
const createLazyImageObserver = () => {
	  // ...
    return lazyImageObserver;
  } else {
    // Possibly fall back to event handlers here
  }
};
const isPageComponent = name => name.contains('[페이지를 구분할 수 있는 키]');
const mixin = {
  data: () => ({
    lazyImageObserver: null,
  }),
  mounted () {
    if (isPageComponent(this._name)) {
      this.lazyImageObserver = createLazyImageObserver();
    }
  },
  destroyed () {
    if (isPageComponent(this._name)) {
      this.lazyImageObserver.disconnect();
    }
  },
};

Vue.mixin(mixin);

위와 같이 Mixin을 플러그인으로 설정하여, 전역으로 선언해주는 방법이 있다.

매 컴포넌트의 mounted 시점에서 해당 로직을 수행할 수 있는데, 만약 페이지 단위에서만 수행하고 싶다면 페이지를 구분할 수 있는 키를 name으로 설정하여 필터링하는 방법이 있다.

 

하지만, 전역으로 사용되는 Mixin은 모든 Vue 인스턴스에 영향을 미치므로 사용을 지양하고 있다.

 

따라서 코드의 중복을 피하고 싶다면 3번, 유지보수 방면에서 코드의 안정성을 추구한다면 1번을 추천할 수 있겠다.


Reference

 

SPA & Routing | PoiemaWeb

단일 페이지 애플리케이션(Single Page Application, SPA)는 모던 웹의 패러다임이다. SPA는 기본적으로 단일 페이지로 구성되며 기존의 서버 사이드 렌더링과 비교할 때, 배포가 간단하며 네이티브 앱과

poiemaweb.com

https://v3.ko.vuejs.org/api/options-lifecycle-hooks.html#beforemount

 

Mixins | Cracking Vue.js

믹스인 믹스인(Mixins)은 여러 컴포넌트 간에 공통으로 사용하고 있는 로직, 기능들을 재사용하는 방법입니다. 믹스인에 정의할 수 있는 재사용 로직은 data, methods, created 등과 같은 컴포넌트 옵션

joshua1988.github.io

 

반응형

댓글