カスタムディレクティブ
はじめに
コアに設定されているデフォルトのディレクティブのセット(v-model
や v-show
など)に加え、Vue では独自のカスタムディレクティブを登録することができます。
Vue におけるコードの再利用として 2 つの方法を紹介してきました:コンポーネントとコンポーザブルです。コンポーネントはメインブロックの構築、コンポーザブルはステートフルなロジックの再利用に重点を置いています。一方、カスタムディレクティブは主にプレーンな要素に対する低レベルの DOM アクセスを伴うロジックを再利用することを目的としています。
カスタムディレクティブは、コンポーネントと同様のライフサイクルフックを含むオブジェクトとして定義されます。フックは、ディレクティブがバインドされている要素を受け取ります。以下は Vue によって要素が DOM に挿入されたときに入力欄にフォーカスするディレクティブの例です:
vue
<script setup>
// テンプレート内で v-focus が有効になります
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
ページの他の場所をクリックしていないと仮定すると、上の入力欄は自動的にフォーカスされるはずです。このディレクティブはページロード時だけでなく、Vue によって要素が動的に挿入されたときにも機能するため、autofocus
属性よりも便利です。
<script setup>
では、接頭辞が v
で始まるキャメルケースの変数をカスタムディレクティブとして使用することができます。上の例では、vFocus
はテンプレート内で v-focus
として使用できます。
<script setup>
を使用しない場合、カスタムディレクティブは directives
オプションを使用して登録することができます:
js
export default {
setup() {
/*...*/
},
directives: {
// テンプレート内で v-focus が有効になります
focus: {
/* ... */
}
}
}
また、カスタムディレクティブをアプリケーションレベルでグローバル登録することもよくあります:
js
const app = createApp({})
// 全てのコンポーネントで v-focus が使用可能
app.directive('focus', {
/* ... */
})
TIP
カスタムディレクティブは DOM を直接操作することでしか必要な機能を実現できない場合にのみ使用してください。v-bind
のような組み込みディレクティブを使用した宣言的なテンプレートは、効率的かつサーバーレンダリングフレンドリーです。可能なかぎり組み込みディレクティブを使用することをおすすめします。
ディレクティブフック
ディレクティブ定義オブジェクトは、いくつかのフック関数(すべて任意です)を提供することができます:
js
const myDirective = {
// バインドされた要素の属性や
// イベントリスナーが適用される前に呼ばれます
created(el, binding, vnode, prevVnode) {
// 引数の詳細については下を参照してください
},
// 要素が DOM に挿入される直前に呼ばれます
beforeMount(el, binding, vnode, prevVnode) {},
// バインドされた要素の親コンポーネントと
// そのすべての子要素がマウントされたときに呼び出されます
mounted(el, binding, vnode, prevVnode) {},
// 親コンポーネントが更新される前に呼ばれます
beforeUpdate(el, binding, vnode, prevVnode) {},
// 親コンポーネントとすべての子要素が
// 更新された後に呼ばれます
updated(el, binding, vnode, prevVnode) {},
// 親コンポーネントがアンマウントされる前に呼ばれます
beforeUnmount(el, binding, vnode, prevVnode) {},
// 親コンポーネントのアンマウント時に呼ばれます
unmounted(el, binding, vnode, prevVnode) {}
}
フックの引数
ディレクティブフックには以下の引数が渡されます:
el
: ディレクティブがバインドされている要素。DOM を直接操作するために使用されます。binding
: 以下のプロパティを含むオブジェクト。value
: ディレクティブに渡される値。例えばv-my-directive="1 + 1"
の場合、値は2
となります。oldValue
: 更新前の値。beforeUpdate
とupdated
でのみ利用可能です。値が変更されているかどうかに関係なく利用できます。arg
: ディレクティブに渡される引数がある場合に存在する引数。例えばv-my-directive:foo
の場合、引数は"foo"
となります。modifiers
: 修飾子がある時に、それを含むオブジェクト。例えばv-my-directive.foo.bar
の場合、修飾子オブジェクトは{ foo: true, bar: true }
となります。instance
: ディレクティブが使用されるコンポーネントのインスタンス。dir
: ディレクティブ定義オブジェクト。
vnode
: バインドされた要素を表す基礎となる VNode。prevNode
: 前のレンダリングからバインドされた要素を表す VNode。beforeUpdate
とupdated
フックでのみ利用可能です。
例として、次のようなディレクティブの使い方を考えてみましょう:
template
<div v-example:foo.bar="baz">
引数 binding
は以下のような構造のオブジェクトになります:
js
{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` の値 */,
oldValue: /* 前回の更新による `baz` の値 */
}
組み込みのディレクティブと同様に、カスタムディレクティブの引数も動的にできます。例えば:
template
<div v-example:[arg]="value"></div>
ここでは、ディレクティブの引数は、コンポーネントの状態にある arg
プロパティに基づいてリアクティブに更新されます。
注意
el
以外のディレクティブの引数は、読み取り専用として扱い、決して変更しないようにしましょう。フック間で情報を共有する必要がある場合は、要素の dataset を通して行うことを推奨します。
関数のショートハンド
よくあることとして、カスタムディレクティブが mounted
と updated
に対して同じ動作をさせ、他のフックを必要としないことがあります。このような場合、ディレクティブを関数として定義することができます:
template
<div v-color="color"></div>
js
app.directive('color', (el, binding) => {
// `mounted` と `updated` の両方で呼ばれます
el.style.color = binding.value
})
オブジェクトリテラル
ディレクティブが複数の値を必要とする場合、JavaScript のオブジェクトリテラルを渡すこともできます。ディレクティブは有効な JavaScript の式はなんでも引き受けられることを覚えておきましょう。
template
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
js
app.directive('demo', (el, binding) => {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
コンポーネントでの使い方
コンポーネントで使用すると、フォールスルー属性と同様にカスタムディレクティブは常にコンポーネントのルートノードに適用されます。
template
<MyComponent v-demo="test" />
template
<!-- template of MyComponent -->
<div> <!-- v-demo directive will be applied here -->
<span>My component content</span>
</div>
コンポーネントは潜在的に複数のルートノードを持つ可能性があることに注意してください。複数のルートを持つコンポーネントに適用された場合、ディレクティブは無視され、警告が投げられます。属性とは異なり、ディレクティブは v-bind="$attrs"
で別の要素に渡すことができません。一般に、カスタムディレクティブをコンポーネントで使用することはお勧めしません。