[Vue3] Vue3에서 Directive를 활용하여 lazy-loading 편하게 쓰기

쭌로그·2025년 1월 23일
1

Vue3

목록 보기
3/3
post-thumbnail

Custom Directive란?

Vue.js 3에서 Custom Directive(사용자 정의 디렉티브)는 DOM 요소에 직접적으로 특정 동작을 추가하거나 제어하기 위해 사용하는 기능입니다. Vue.js에는 기본 디렉티브(v-model, v-if, v-for 등)가 있지만, 상황에 따라 특정 요구 사항에 맞는 동작을 구현해야 할 때 Custom Directive를 활용할 수 있습니다. v-접두어를 사용하여 만들 수 있으며 반복적인 작업을 v-접두어를 통해 줄일 수 있습니다.

Vue3 공식 블로그

전역적, 지역적 Directive

1. 전역적 Directive

const app = createApp({})

// 모든 컴포넌트에서 v-focus를 사용할 수 있도록 합니다.
app.directive('focus', {
  /* ... */
})

2. 지역적 Directive

<script setup>
// 템플릿에서 v-focus로 활성화 가능
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>

또한 Directive는 여러 훅을 사용할 수 있습니다. 모든 훅은 필수로 입력해야 하는것이 아닌, 선택적으로 사용할 수 있습니다.

const myDirective = {
  // 바인딩된 엘리먼트의 속성 또는
  // 이벤트 리스너가 적용되기 전에 호출됩니다.
  created(el, binding, vnode, prevVnode) {
    // 인자에 대한 자세한 내용은 아래를 참고.
  },
  // 엘리먼트가 DOM에 삽입되기 직전에 호출됩니다.
  beforeMount(el, binding, vnode, prevVnode) {},
  // 바인딩된 엘리먼트의 부모 컴포넌트 및
  // 모든 자식 컴포넌트의 mounted 이후에 호출됩니다.
  mounted(el, binding, vnode, prevVnode) {},
  // 부모 컴포넌트의 updated 전에 호출됩니다.
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 바인딩된 엘리먼트의 부모 컴포넌트 및
  // 모든 자식 컴포넌트의 updated 이후에 호출됩니다.
  updated(el, binding, vnode, prevVnode) {},
  // 부모 컴포넌트의 beforeUnmount 이후에 호출됩니다.
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 부모 컴포넌트의 unmounted 전에 호출됩니다.
  unmounted(el, binding, vnode, prevVnode) {}
}

Hook의 파라메터

디렉티브 훅에는 다음 인자가 전달됩니다:

- el: 디렉티브가 바인딩된 엘리먼트입니다. DOM을 직접 조작하는 데 사용할 수 있습니다.
- binding: 다음 속성을 포함하는 객체입니다.
	- value: 디렉티브에 전달된 값입니다. 예를 들어 v-my-directive="1 + 1"에서 - value는 2입니다.
	- oldValue: 이것은 beforeUpdate 및 updated에서만 사용할 수 있습니다. 값이 변경되었는지 여부에 관계없이 사용 가능합니다.
	- arg: 디렉티브에 전달된 인자(있는 경우). 예를 들어 v-my-directive:foo에서 인자는 "foo"입니다.
	- modifiers: 수식어가 있는 경우 수식어를 포함하는 객체입니다. 예를 들어 v-my-directive.foo.bar에서 수식어 객체는 { foo: true, bar: true }입니다.
	- instance: 디렉티브가 사용되는 컴포넌트의 인스턴스입니다.
	- dir: 디렉티브를 정의하는 객체
- vnode: 바인딩된 엘리먼트를 나타내는 기본 VNode.
- prevVnode: 이전 렌더링에서 바인딩된 엘리먼트를 나타내는 VNode입니다. beforeUpdate 및 updated 훅에서만 사용할 수 있습니다.

저는 반복적으로 Interaction Observer를 사용할 것이기 때문에 전역적으로 사용하겠습니다.

lazy load


// lazyLoad.js
const lazyLoad = {
	mounted(el) {
    	const setBackgroundImage = () => {
        	if(el.className.includes('lazy')) {
            	//dataset에 들어있는 url을 바인딩
            	el.style.backgroundImage = `url(${el.dataset.url})`;
            }
        }

        
		const setObserver = () => {
        	const obOptions = {
               root: null,
        		rootMargin: '0px 0px 100px 0px', 
        		threshold: 0.5, // 관찰 대상의 50% 이상이 보일 때 콜백 호출
            };
            const observer = new IntersectionObserver((iter)=>{
                  iter.forEach((item) => {
                    	if(item.isIntersecting) {
                      		setBackgroundImage();
                      		observer.unobserve(el);
                      }
                  });
            }, obOptions)
            observer.observe(el);                                     
      }
      // IntersectionObserver가 없는 브라우저에서 호환을 위한 처리.
      window.IntersectionObserver ? setObserver() : setBackgroundImage()
  }
}
export default lazyLoad;

directive 등록

전역 Directive는 main.js에서 등록할 수 있습니다.

import { createApp } from "vue"
import laztLoad from '@/directives/lazyLoad.ts'
//main.ts
const app = createApp();

//directive 메서드를 사용하여 등록
//v-lazy와 같은 형태로 사용.
app.directive('lazy', lazyLoad);

지역적으로 사용하고 싶다면 script setup에서 v 접두사로 시작하는 모든 camelCase 변수를 커스텀 디렉티브로 사용할 수 있습니다.

<script setup>
//v 접두사로 시작하여 디렉티브 설정
const vLazy = {
	mounted(el) {
    	const setBackgroundImage = () => {
        	if(el.className.includes('lazy')) {
            	//dataset에 들어있는 url을 바인딩
            	el.style.backgroundImage = `url(${el.dataset.url})`;
            }
        }

        
		const setObserver = () => {
        	const obOptions = {
               root: null,
        		rootMargin: '0px 0px 100px 0px', 
        		threshold: 0.5, // 관찰 대상의 50% 이상이 보일 때 콜백 호출
            };
            const observer = new IntersectionObserver((iter)=>{
                  iter.forEach((item) => {
                    	if(item.isIntersecting) {
                      	setBackgroundImage();
                      	observer.unobserve(el);
                      }
                  });
            }, obOptions)
            observer.observe(el);                                     
      }
      // IntersectionObserver가 없는 브라우저에서 호환을 위한 처리.
      window.IntersectionObserver ? setObserver() : setBackgroundImage()
  }
}
</script>

<template>
	<div v-lazy :data-src="`@/assets/someImage.webp`" />
</template>
profile
매일 발전하는 프론트엔드 개발자

0개의 댓글