--- title: Directive Best Practices impact: MEDIUM impactDescription: Custom directives are powerful but easy to misuse; following patterns prevents leaks, invalid usage, and unclear abstractions type: best-practice tags: [vue3, directives, custom-directives, composition, typescript] --- # Directive Best Practices **Impact: MEDIUM** - Directives are for low-level DOM access. Use them sparingly, keep them side-effect safe, and prefer components or composables when you need stateful or reusable UI behavior. ## Task List - Use directives only when you need direct DOM access - Do not mutate directive arguments or binding objects - Clean up timers, listeners, and observers in `unmounted` - Register directives in ` ``` ## Clean Up Side Effects in `unmounted` Any timers, listeners, or observers must be removed to avoid leaks. ```ts const vResize = { mounted(el) { const observer = new ResizeObserver(() => {}) observer.observe(el) el._observer = observer }, unmounted(el) { el._observer?.disconnect() } } ``` ## Prefer Function Shorthand for Single-Hook Directives If you only need `mounted`/`updated`, use the function form. ```ts const vAutofocus = (el) => el.focus() ``` ## Use the `v-` Prefix and Script Setup Registration ```vue ``` ## Type Custom Directives in TypeScript Projects Use `Directive` so `binding.value` is typed, and augment Vue's template types so directives are recognized in SFC templates. **BAD:** ```ts // Untyped directive value and no template type augmentation export const vHighlight = { mounted(el, binding) { el.style.backgroundColor = binding.value } } ``` **GOOD:** ```ts import type { Directive } from 'vue' type HighlightValue = string export const vHighlight = { mounted(el, binding) { el.style.backgroundColor = binding.value } } satisfies Directive declare module 'vue' { interface ComponentCustomProperties { vHighlight: typeof vHighlight } } ``` ## Handle SSR with `getSSRProps` Directive hooks such as `mounted` and `updated` do not run during SSR. If a directive sets attributes/classes that affect rendered HTML, provide an SSR equivalent via `getSSRProps` to avoid hydration mismatches. **BAD:** ```ts const vTooltip = { mounted(el, binding) { el.setAttribute('data-tooltip', binding.value) el.classList.add('has-tooltip') } } ``` **GOOD:** ```ts const vTooltip = { mounted(el, binding) { el.setAttribute('data-tooltip', binding.value) el.classList.add('has-tooltip') }, getSSRProps(binding) { return { 'data-tooltip': binding.value, class: 'has-tooltip' } } } ``` ## Prefer Declarative Templates When Possible If a standard attribute or binding works, use it instead of a directive. ## Decide Between Directives and Components Use a directive for DOM-level behavior. Use a component when behavior affects structure, state, or rendering.