Custom Directives - 低レベルDOM操作の再利用
ロードマップ: Vue.js学習ロードマップ
What(何についてか)
Custom Directive は、Vue の組み込み directive(v-model, v-show など)に加えて、アプリケーション独自の directive を定義し、plain DOM element に対する低レベル DOM 操作を再利用する仕組み である。
Vue における再利用手段の役割分担は次のように整理できる。
- Component: UI 構造と見た目の再利用
- Composable: stateful logic の再利用
- Custom Directive: direct DOM manipulation を伴う要素単位ロジックの再利用
代表例は、要素が DOM に挿入された時点で focus() を呼ぶ v-focus や、要素に class を付与する v-highlight である。
Why(なぜ必要か)
Vue は宣言的 UI を基本とするが、すべての振る舞いを props, template, composable だけで自然に表現できるわけではない。 特に以下のようなケースでは、DOM 要素そのものに対する命令的な操作が必要になる。
focus()の呼び出しscrollIntoView()の呼び出しIntersectionObserverやResizeObserverの接続対象管理- 3rd party DOM ライブラリの要素適用
- 特定要素の直接的な style / class / 属性制御
このようなケースで custom directive を使うと、DOM 直接操作をテンプレート上の再利用可能な API に変換できる。
一方で、通常の状態管理や class/style 切り替え、データ取得、複数要素をまたぐアプリケーションロジックは custom directive の担当ではない。これらは built-in directive, component, composable, state management で扱う方が自然である。
How(どう動くか)
基本定義
<script setup> では、v で始まる camelCase 変数をそのまま custom directive としてテンプレートで使える。
<script setup lang="ts">
const vHighlight = {
mounted: (el: HTMLElement) => {
el.classList.add('is-highlight')
}
}
</script>
<template>
<p v-highlight>This sentence is important!</p>
</template>グローバルに使いたい場合は app.directive() で登録する。
app.directive('highlight', {
mounted: (el) => {
el.classList.add('is-highlight')
}
})いつ使うべきか
Custom directive は、目的の機能が direct DOM manipulation でしか自然に実現しにくい時に限定して使う。
例えば v-focus は典型的な適用例である。
<script setup lang="ts">
const vFocus = {
mounted: (el: HTMLInputElement) => el.focus()
}
</script>
<template>
<input v-focus />
</template>autofocus 属性と異なり、この directive はページ初回表示時だけでなく、Vue によって後から動的に挿入された要素にも適用できる。
Directive Hooks
Directive は object として定義し、要素に対する処理タイミングごとに hook を定義できる。
const vExample = {
created(el: HTMLElement) {},
beforeMount(el: HTMLElement) {},
mounted(el: HTMLElement) {},
beforeUpdate(el: HTMLElement) {},
updated(el: HTMLElement) {},
beforeUnmount(el: HTMLElement) {},
unmounted(el: HTMLElement) {}
}主な役割は次の通りである。
mounted: 要素が DOM に挿入された後の初期適用updated: binding 値変化後の再適用unmounted: event listener や observer の cleanup
component lifecycle に似ているが、主語は component ではなく bound element である点が重要である。
Hook Arguments
Directive hook では主に el と binding を使う。
el: directive が紐付いた実際の DOM 要素binding: テンプレートから渡された情報の集合vnode: Vue 内部の Virtual DOM ノードprevVnode: update 系 hook で使える前回の VNode
binding は以下の情報を持つ。
value: 現在値oldValue: 前回値(beforeUpdate,updated)arg:v-example:fooのfoomodifiers:v-example.foo.barの{ foo: true, bar: true }instance: directive を使用している component instancedir: directive 定義オブジェクト
例えば以下の template:
<div v-example:foo.bar="baz"></div>は概念的に次の入力を与える。
{
arg: 'foo',
modifiers: { bar: true },
value: baz,
oldValue: /* previous baz */
}modifiers は名前付きフラグ集合であり、通常の使い方では順序を持たない。
したがって v-example.foo.bar と v-example.bar.foo は実質的に同義である。
el.dataset による補助情報の保持
binding や vnode は読み取り専用として扱うべきである。
Hook 間で小さな情報を共有したい場合は、el.dataset を使う。
el.dataset は要素の data-* 属性を JS から操作する API である。
const vExample = {
mounted(el: HTMLElement) {
el.dataset.initialized = 'true'
},
updated(el: HTMLElement) {
if (el.dataset.initialized === 'true') {
// 初期化済みとして扱う
}
},
unmounted(el: HTMLElement) {
delete el.dataset.initialized
}
}これは、その DOM 要素専用の小さなメタデータ置き場として機能する。
Function Shorthand
mounted と updated に同じ処理を書くだけなら、object ではなく関数で短く書ける。
この shorthand は mounted と updated の両方に適用される。
<script setup lang="ts">
const vColor = (el: HTMLElement, binding: { value: string }) => {
el.style.color = binding.value
}
</script>
<template>
<div v-color="'red'">hello</div>
</template>これは、要素に対して現在値を毎回同じ方法で反映するだけの directive に向いている。 一方で cleanup や hook ごとの差異が必要な場合は object 形式で定義する。
Object Literal による複数値の受け渡し
directive の値には単一値だけでなく object literal も渡せる。
これにより、複数の設定項目を 1 つの binding.value にまとめられる。
<div v-demo="{ color: 'white', text: 'hello!' }"></div>const vDemo = (
el: HTMLElement,
binding: { value: { color: string; text: string } }
) => {
el.style.color = binding.value.color
el.textContent = binding.value.text
}入力の使い分けは以下のように考えると整理しやすい。
arg: モードや対象種別modifiers: on/off フラグvalue: 本体データ- object literal: 本体データが複数項目ある場合の構造化入力
Component への適用制約
Custom directive を component に対して使うのは非推奨である。
<MyComponent v-demo="test" />directive は component そのものではなく、その root node に適用される。 このため、component の内部実装に依存して壊れやすい。
- root element が変わると適用先が変わる
- 複数 root を持つ component では directive が無視され warning が出る
- directive を適用したい要素が root とは限らない
そのため、custom directive は plain DOM element 向けの道具 として使うべきである。
Composition API における位置づけ
Custom directive は、Vue の宣言的な仕組みで表現しきれない DOM 直接操作を局所化する手段である。
flowchart TD A["やりたい再利用は何か"] --> B["UI 構造や見た目"] A --> C["状態や副作用ロジック"] A --> D["要素への直接DOM操作"] B --> E["Component"] C --> F["Composable"] D --> G["Custom Directive"]
Options API → Composition API 差分(補足)
| 項目 | 従来の見方 | Vue 3 / Composition API 時代の見方 |
|---|---|---|
| DOM 再利用 | component 内部実装や mixin に寄せがち | plain element に対して directive で局所化 |
| ロジック共有 | mixin が混ざりやすい | stateful logic は composable、DOM 直接操作は directive |
| 適用先 | component にも何となく貼りたくなる | directive は基本 plain DOM element 向け |
| 記法 | object 中心 | <script setup> で vXxx / function shorthand が自然 |
Key Concepts
| 用語 | 説明 |
|---|---|
| Custom Directive | plain DOM element への低レベル DOM 操作を再利用する仕組み |
| Direct DOM Manipulation | focus(), scrollIntoView(), class/style 直接変更など命令的要素操作 |
| Directive Hooks | mounted, updated, unmounted など要素処理タイミングごとの hook |
binding.value | directive に渡された本体値 |
binding.arg | v-example:foo の foo |
binding.modifiers | v-example.foo.bar のようなフラグ集合 |
| Function Shorthand | mounted / updated が同一処理の時に使う短縮記法 |
el.dataset | data-* 属性経由で要素に小さな補助情報を保持する API |
vnode | Vue 内部で描画対象を表す Virtual DOM ノード |
実務での判断軸
- 見た目や構造を再利用したいなら component を使う
- 状態や副作用ロジックを再利用したいなら composable を使う
- DOM 要素へ直接命令したいなら custom directive を使う
- まず declarative に書けるか検討し、どうしても要素直接操作が必要な場合だけ directive を選ぶ
Custom directive は強力だが、Vue の基本設計から一段低いレイヤーへ降りる道具である。したがって、常用するよりも「必要な時に限定して正確に使う」設計が望ましい。