FrontEnd/Vue.js

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

푸고배 2022. 11. 7. 21:25

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

 

반응형