Vue 3 코드 재사용성 극대화를 위한 커스텀 디렉티브, 믹스인, 플러그인 활용 가이드

Vue 3 재사용 가능 아키텍처 개요

Vue.js 프로젝트의 규모가 커짐에 따라 코드 중복을 줄이고 유지보수성을 높이는 재사용 패턴의 중요성이 부각됩니다. Vue 3에서는 컴포넌트 외에도 커스텀 디렉티브(Custom Directives), 믹스인(Mixins), 플러그인(Plugins) 등의 메커니즘을 통해 비즈니스 로직과 DOM 조작 로직을 효과적으로 추상화할 수 있습니다.

1. 커스텀 디렉티브 (Custom Directives)

커스텀 디렉티브는 반복되는 DOM 조작 로직을 선언적인 방식으로 캡슐화할 때 유용합니다. Vue 3의 커스텀 디렉티브는 컴포넌트의 라이프사이클 훅과 유사한 구조를 가집니다.

주요 라이프사이클 훅

  • created: 요소의 속성이나 이벤트 리스너가 적용되기 전에 호출됩니다.
  • beforeMount: 요소가 DOM에 삽입되기 직전에 호출됩니다.
  • mounted: 요소가 부모 노드에 삽입된 후 호출됩니다. 초기 DOM 설정에 적합합니다.
  • beforeUpdate: 요소 자체가 업데이트되기 전에 호출됩니다.
  • updated: 요소와 자식 요소들이 모두 업데이트된 후 호출됩니다.
  • beforeUnmount: 요소가 언마운트되기 직전에 호출됩니다.
  • unmounted: 요소가 언마운트되고 바인딩이 해제된 후 호출됩니다. 이벤트 리스너 제거 등 정리 작업에 사용됩니다.

1.1 전역 및 로컬 디렉티브 등록

디렉티브는 애플리케이션 전체에서 사용할 수 있도록 전역으로 등록하거나, 특정 컴포넌트 내에서만 사용하도록 로컬로 등록할 수 있습니다.

전역 등록 예시 (자동 포커스 디렉티브):

<div id="app">
  <input type="text" v-auto-focus />
</div>

<script>
  const app = Vue.createApp({});

  // 전역 디렉티브 등록
  app.directive('auto-focus', {
    mounted(element) {
      element.focus();
    }
  });

  app.mount('#app');
</script>

로컬 등록 예시:

<div id="app">
  <textarea v-auto-resize></textarea>
</div>

<script>
  const app = Vue.createApp({
    directives: {
      autoResize: {
        mounted(el) {
          el.style.overflow = 'hidden';
          el.addEventListener('input', () => {
            el.style.height = 'auto';
            el.style.height = el.scrollHeight + 'px';
          });
        }
      }
    }
  }).mount('#app');
</script>

1.2 디렉티브 파라미터와 수정자

binding 객체를 통해 디렉티브에 전달된 값, 인자, 수정자 등을 참조할 수 있습니다.

<div id="app">
  <p v-text-color:background.light="themeColor">테스트 텍스트</p>
</div>

<script>
  const app = Vue.createApp({
    data() {
      return { themeColor: '#3498db' };
    },
    directives: {
      textColor: {
        mounted(el, binding) {
          const colorValue = binding.value;
          const targetProp = binding.arg || 'color';
          
          if (binding.modifiers.light) {
            el.style[targetProp] = colorValue + '33'; // 투명도 추가
          } else {
            el.style[targetProp] = colorValue;
          }
        }
      }
    }
  }).mount('#app');
</script>

2. 믹스인 (Mixins)

믹스인은 Options API 환경에서 여러 컴포넌트 간에 로직(데이터, 메서드, 라이프사이클 훅 등)을 공유하기 위한 패턴입니다. Vue 3에서는 Composition API의 composables가 더 권장되지만, 기존 Options API 코드베이스에서는 여전히 믹스인이 활용됩니다.

2.1 기본 사용법

공통된 상태와 로직을 객체로 추출한 후, 컴포넌트의 mixins 배열에 포함시킵니다.

<div id="app">
  <p>현재 페이지: {{ currentPage }}</p>
  <button @click="goToNextPage">다음 페이지</button>
</div>

<script>
  // 페이지네이션 로직을 담은 믹스인
  const paginationMixin = {
    data() {
      return {
        currentPage: 1,
        pageSize: 20
      };
    },
    methods: {
      goToNextPage() {
        this.currentPage += 1;
      }
    }
  };

  const app = Vue.createApp({
    mixins: [paginationMixin]
  }).mount('#app');
</script>

2.2 옵션 병합 우선순위

컴포넌트와 믹스인에 동일한 옵션이 존재할 경우, Vue는 특정 규칙에 따라 이를 병합합니다.

  • data: 컴포넌트의 데이터가 믹스인의 데이터보다 우선합니다. (객체 프로퍼티 수준에서 병합)
  • methods, computed, watch: 컴포넌트의 메서드가 믹스인의 메서드를 덮어씁니다.
  • 라이프사이클 훅: 믹스인의 훅이 먼저 실행되고, 그 다음 컴포넌트의 훅이 실행됩니다.
<script>
  const baseMixin = {
    data() {
      return { status: 'inactive', count: 0 };
    },
    methods: {
      fetchStatus() { console.log('Mixin: fetchStatus'); }
    },
    mounted() {
      console.log('Mixin: mounted');
    }
  };

  Vue.createApp({
    mixins: [baseMixin],
    data() {
      return { status: 'active' }; // 컴포넌트 데이터가 우선됨
    },
    methods: {
      fetchStatus() { console.log('Component: fetchStatus'); } // 컴포넌트 메서드가 덮어씀
    },
    mounted() {
      console.log('Component: mounted'); // Mixin mounted 이후 실행됨
    }
  }).mount('#app');
</script>

2.3 전역 믹스인

app.mixin()을 사용하면 이후 생성되는 모든 컴포넌트에 옵션을 주입할 수 있습니다. 하지만 의도치 않은 부작용을 방지하기 위해 플러그인 내부에서만 제한적으로 사용해야 합니다.

3. 플러그인 (Plugins)

플러그인은 애플리케이션 레벨에서 전역 기능을 추가할 때 사용합니다. 라이브러리 배포나 사내 공통 모듈 초기화 시 유용합니다.

3.1 플러그인 정의 및 등록

플러그인은 install 메서드를 가진 객체이거나, install 함수 그 자체일 수 있습니다. app.use()를 통해 등록합니다.

<div id="app">
  <button @click="trackEvent">이벤트 추적</button>
</div>

<script>
  // 애널리틱스 플러그인 정의
  const AnalyticsPlugin = {
    install(app, options) {
      const trackingId = options?.id || 'DEFAULT_ID';

      // 전역 속성 추가
      app.config.globalProperties.$track = (eventName) => {
        console.log(`[Analytics ${trackingId}] Event tracked: ${eventName}`);
      };

      // 전역 디렉티브 추가
      app.directive('track-click', {
        mounted(el, binding) {
          el.addEventListener('click', () => {
            app.config.globalProperties.$track(binding.value);
          });
        }
      });
    }
  };

  const app = Vue.createApp({
    methods: {
      trackEvent() {
        this.$track('button_click');
      }
    }
  });

  // 플러그인 등록 및 옵션 전달
  app.use(AnalyticsPlugin, { id: 'UA-12345' });
  app.mount('#app');
</script>

태그: vue3 CustomDirectives Mixins VuePlugins OptionsAPI

5월 29일 22:02에 게시됨