커스텀 디렉티브
커스텀 디렉티브는 DOM 엘리먼트에 직접 접근해야 하는 로직을 재사용 가능한 형태로 추출하는 방법입니다. 컴포넌트로 해결하기 어려운 저수준 DOM 조작에 적합합니다.
면접에서 직접 물어보는 경우는 드물지만, "DOM 접근이 필요한 반복 로직을 어떻게 재사용하나요?"라는 질문에 활용할 수 있습니다.
기본 사용법
<script setup lang="ts">
// script setup에서 v 접두사로 시작하는 camelCase 변수는 자동으로 디렉티브로 인식
const vFocus = {
mounted: (el: HTMLElement) => {
el.focus()
}
}
</script>
<template>
<!-- v-focus 디렉티브 사용 -->
<input v-focus placeholder="자동 포커스" />
</template>
디렉티브 훅 생명주기
import type { Directive } from 'vue'
const vExample: Directive<HTMLElement, string> = {
// 바인딩된 엘리먼트가 DOM에 삽입되기 전
created(el, binding, vnode) {
console.log('created')
},
// 엘리먼트가 DOM에 삽입된 직후
mounted(el, binding, vnode) {
console.log('mounted')
},
// 부모 컴포넌트가 업데이트되기 전
beforeUpdate(el, binding, vnode, prevVnode) {
console.log('beforeUpdate')
},
// 부모 컴포넌트와 자식들이 모두 업데이트된 후
updated(el, binding, vnode, prevVnode) {
console.log('updated')
},
// 부모 컴포넌트가 언마운트되기 전
beforeUnmount(el, binding, vnode) {
console.log('beforeUnmount')
},
// 부모 컴포넌트가 언마운트된 후
unmounted(el, binding, vnode) {
console.log('unmounted')
}
}
binding 객체
const vDemo: Directive = {
mounted(el, binding) {
// v-demo:arg.modifier1.modifier2="value" 에서
console.log(binding.value) // value — 전달된 값
console.log(binding.oldValue) // 이전 값 (updated에서만)
console.log(binding.arg) // 'arg' — 인자
console.log(binding.modifiers) // { modifier1: true, modifier2: true }
console.log(binding.instance) // 컴포넌트 인스턴스
console.log(binding.dir) // 디렉티브 객체 자체
}
}
실전 예제
v-click-outside — 외부 클릭 감지
// directives/clickOutside.ts
import type { Directive } from 'vue'
export const vClickOutside: Directive<HTMLElement, () => void> = {
mounted(el, binding) {
const handler = (event: Event) => {
if (!el.contains(event.target as Node)) {
binding.value()
}
}
// 핸들러를 엘리먼트에 저장하여 unmounted에서 제거
;(el as any).__clickOutsideHandler = handler
document.addEventListener('click', handler)
},
unmounted(el) {
document.removeEventListener('click', (el as any).__clickOutsideHandler)
delete (el as any).__clickOutsideHandler
}
}
<script setup lang="ts">
import { ref } from 'vue'
import { vClickOutside } from '@/directives/clickOutside'
const isDropdownOpen = ref(false)
const closeDropdown = () => { isDropdownOpen.value = false }
</script>
<template>
<div v-click-outside="closeDropdown">
<button @click="isDropdownOpen = !isDropdownOpen">메뉴</button>
<ul v-if="isDropdownOpen">
<li>항목 1</li>
<li>항목 2</li>
</ul>
</div>
</template>
v-intersection — Intersection Observer
// directives/intersection.ts
import type { Directive } from 'vue'
export const vIntersection: Directive<HTMLElement, (isIntersecting: boolean) => void> = {
mounted(el, binding) {
const observer = new IntersectionObserver(([entry]) => {
binding.value(entry.isIntersecting)
}, { threshold: 0.1 })
observer.observe(el)
;(el as any).__observer = observer
},
unmounted(el) {
;(el as any).__observer?.disconnect()
}
}
v-tooltip — 툴팁
// directives/tooltip.ts
import type { Directive } from 'vue'
export const vTooltip: Directive<HTMLElement, string> = {
mounted(el, binding) {
el.setAttribute('title', binding.value)
el.style.cursor = 'help'
},
updated(el, binding) {
el.setAttribute('title', binding.value)
}
}
전역 등록
// main.ts
import { createApp } from 'vue'
import { vClickOutside } from './directives/clickOutside'
import { vTooltip } from './directives/tooltip'
const app = createApp(App)
app.directive('click-outside', vClickOutside)
app.directive('tooltip', vTooltip)
app.mount('#app')
축약 문법
mounted와 updated에서 같은 동작을 할 때 함수로 간결하게 작성할 수 있습니다.
// 축약 — mounted와 updated 모두에서 동일하게 실행
const vColor: Directive<HTMLElement, string> = (el, binding) => {
el.style.color = binding.value
}
<template>
<p v-color="'red'">빨간 텍스트</p>
<p v-color="dynamicColor">동적 색상</p>
</template>
컴포넌트에 디렉티브 사용
컴포넌트에 디렉티브를 사용하면 Fallthrough Attributes처럼 루트 엘리먼트에 적용됩니다. 다중 루트 컴포넌트에서는 사용이 권장되지 않습니다.
디렉티브 vs Composable
| 항목 | 커스텀 디렉티브 | Composable |
|---|---|---|
| 용도 | DOM 직접 조작 | 상태 + 로직 재사용 |
| 접근 대상 | DOM 엘리먼트 | 반응형 데이터 |
| 사용법 | v-directive | useSomething() |
| 적합한 경우 | 외부 클릭, 포커스, 스크롤 | 폼 검증, API 호출, 타이머 |
대부분의 경우 Composable이 더 적합 합니다. 디렉티브는 DOM에 직접 접근해야 할 때만 사용하세요.
면접 팁
- 커스텀 디렉티브는 저수준 DOM 조작의 재사용 수단입니다. Composable로 해결할 수 있다면 Composable을 우선하세요
v-click-outside는 실무에서 가장 많이 만드는 커스텀 디렉티브입니다- unmounted에서 이벤트 리스너를 반드시 제거하는 ** 클린업 패턴 **을 강조하면 좋습니다
요약
커스텀 디렉티브는 DOM 엘리먼트에 대한 저수준 접근이 필요할 때 사용하는 재사용 패턴입니다. 컴포넌트 생명주기와 유사한 훅(mounted, updated, unmounted)을 가지며, binding 객체로 값, 인자, 수식어를 전달받습니다. 대부분의 로직 재사용은 Composable이 더 적합합니다.
댓글 로딩 중...