第19回 Vue.js入門 カスタムディレクティブ

directive プログラミング

こんにちは、コンスキです。

今回は、Vue Routerの内容から一度離れて、カスタムディレクティブというものについて説明します。

カスタムディレクティブとは

カスタムディレクティブとは、自分で作る事ができるオリジナルのディレクティブのことです。

これまでに、Vue.jsにはv-bindやb-on、v-ifといったような様々なディレクティブを紹介してきました。

これらの「元からある」ディレクティブに加えて自分で「カスタム」して作れる、カスタムディレクティブというものがあります。

「コンポーネントのたくさんの要素を、dataプロパティの値の変化に合わせて、もっと自由に設定したい。」というようなときに、自分でディレクティブを作ることができて便利です。

例えば、条件に合わせて、警告用のスタイルを複数の要素に適応したいときに、次のようにすでにあるv-bindディレクティブを要素ひとつひとつに書いてそれの願いを叶えることができます。

<div id="app">
  <p v-bind:style="{border: (isAllowed ? '' : '1px solid red'), color: (isAllowed ? '' : 'red')}">メッセージ1</p>
  <p v-bind:style="{border: (isAllowed ? '' : '1px solid red'), color: (isAllowed ? '' : 'red')}">メッセージ2</p>
  <p v-bind:style="{border: (isAllowed ? '' : '1px solid red'), color: (isAllowed ? '' : 'red')}">メッセージ3</p>
</div>

しかし、複数の要素の共通して書くのであれば、もっとシンプルに書けた方がいいと思います。

そこで、次のように、「v-alert」というようなカスタムディレクティブを作っておけば、シンプルに「v-alert」というディレクティブを要素につけるだけでよくなることだってあります。

<div id="app">
<p v-alert>メッセージ1</p>
<p v-alert>メッセージ2</p>
<p v-alert>メッセージ3</p>

</div>

カスタムディレクティブの使い方

カスタムディレクティブの作成

カスタムディレクティブを使うには、あらかじめ自分でディレクティブを作っておく必要があります。

次のように書くことでカスタムディレクティブを作ることができます。

Vue.directive("ディレクティブの名前", {
  フック名: function (処理に使う引数) {
    //ここに引数を使ったディレクティブが行う処理を書きます
  }
})

上の書き方を見ると、Vue.directiveというAPIを使っていることがわかります。このAPIは引数に2つのものを設定します。

第一引数として渡されているのは、「ディレクティブの名前」で、第二引数として渡されているのは、「ディレクティブを設定するためのオブジェクト」です。

ディレクティブ名

第一引数のディレクティブの名前は、このカスタムディレクティブを使うときにどのように要素に記述するかということを表しています。

このカスタムディレクティブの名前の前に「v-」をつけて要素に書くことで、使うことができます。

例えば、次のようにカスタムディレクティブの名前を「alert」に設定します。

Vue.directive("alert", {
  //省略しています
})

その場合、このディレクティブを使う際には、次のように「v-alert」と要素に書きます。

<要素名 v-alert></要素名>

カスタムディレクティブ設定オブジェクト

フック

次に、第二引数のカスタムディレクティブを設定するためのオブジェクト中身には、「フック名」というプロパティがあります。

このフック名は、カスタムディレクティブによる処理をいつ実行するかというタイミングです。フックには次のような種類があります。

フック名どのようなタイミングか
bindカスタムディレクティブが初めて要素にひも付いた時
insertedカスタムディレクティブがひも付いている要素が親ノードに挿入された時
updateカスタムディレクティブがひも付いた要素より外にあるコンポーネントのVNodeが更新された時
componentUpdatedカスタムディレクティブがひも付いた要素より内側にあるコンポーネントと外側にあるコンポーネントのVNodeがどちらともが更新された時
unbindカスタムディレクティブがひも付いていた要素から外された時

例として、bindフックを使った「v-emphasize」ディレクティブを作ってみます。

HTML側

  <div id="app">
    <p>v-emphasizeディレクティブを使うと<span v-emphasize>強調されます。</span></p>
  </div>

JavaScript側

Vue.directive("emphasize", {
  bind: function (el) {
    el.style.fontWeight = "bold"
    el.style.color = "red"
  }
})

ブラウザに表示すると次のようになります。

フック関数の引数

特定のフックに実行される関数をフック関数と呼びます。先程の例でいうと次の赤い部分がフック関数です。

Vue.directive("emphasize", {
  bind: function (el) {
    el.style.fontWeight = "bold"
    el.style.color = "red"
  }
})

このフック関数には、次のような引数を取ることができます。

引数名内容
elカスタムディレクティブをひも付ける要素のオブジェクト
bindingカスタムディレクティブに与えられた値、引数、修飾子をもつオブジェクト
vnode更新されたあとの仮想DOM
oldVnode更新される前の仮想DOM

bindingには次のプロパティが含まれています。

プロパティ名値の内容
name頭の「v-」を除いた、カスタムディレクティブの名前<要素名 v-この部分></要素名>
valueディレクティブに渡される値<要素名 v-ディレクティブ名=”ここの値”></要素名>
expressionディレクティブに渡される文字列としての値上の場合「ここの値」という文字列 
argディレクティブに渡される引数の値 <要素名 v-ディレクティブ名:ここの値></要素名>
modifiersディレクティブに付けられている修飾子がわかるオブジェクト<要素名 v-ディレク名.修飾子1.修飾子2></要素名>の場合
{修飾子1: true, 修飾子2: true}

updateフックとcomponentUpdatedフックの違い

すでにupdateフックとcomponentUpdateフックの大まかな説明はしました。

フック名どのようなタイミングか
updateカスタムディレクティブがひも付いた要素より外にあるコンポーネントのVNodeが更新された時
componentUpdatedカスタムディレクティブがひも付いた要素より内側にあるコンポーネントと外側にあるコンポーネントのVNodeがどちらともが更新された時

しかし、それだけでは具体的にどんなタイミングの違いがあるのかわかりづらいため、サンプルプログラムを使って、この2つのフックの具体的な違いを説明します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>カスタムディレクティブ</title>
  <script src="https://unpkg.com/vue"></script>
  <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> 
</head>
<body>
  <div id="app">
      <div v-display-vnode>{{ message }}</div>
      <button v-on:click="changeMsg">メッセージを変える</button>
  </div>
  <script>
    Vue.directive("display-child", {
      update: function (el) {
        console.log("update: ", el.innerHTML)
      },
      componentUpdated: function (el) {
        console.log("componentUpdated: ", el.innerHTML)
      }
    })
    var vm = new Vue({
      el: "#app",
      data: {
        message: "こんにちは",
      },
      methods: {
        changeMsg: function () {
          this.message = "See you"
        }
      }
    })
  </script>
</body>
</html>

上のコードをブラウザに表示すると次のようになります。

このときに、Chrome DevToolsのコンソールを見てもログは表示されていません。

「メッセージを変える」というボタンをクリックすることで、v-display-childディレクティブを付けたdiv要素より外にあるコンポーネントであるVueインスタンスのVNodeが更新されます。

また、{{message}}という要素をコンポーネントだとみなすと、v-display-childディレクティブよりうち画にあるコンポーネントのVNodeにも更新が行われます。

これにより、v-display-childディレクティブのupdateフックとcomponentUpdatedフックの両方の処理が行われます。

処理が行われたことを確認するために、Chrome DevToolsのコンソールを見ると次のように表示されます。

v-diplay-childディレクティブをひも付けたdiv要素は共通しているはずなのにも関わらず、updateはメッセージを変更前の「こんにちは」、componentUpdatedではメッセージ変更後の「See you」がdivの子要素として表示されています。

updateフックでdiv要素の子要素を表示するとメッセージ変更前の「こんにちは」が表示されるのは、componentUpdatedフックのタイミングが「カスタムディレクティブがひも付いた要素より内側にあるコンポーネントと外側にあるコンポーネントのVNodeがどちらともが更新された時」であるのに対して、updateフックのタイミングが「カスタムディレクティブがひも付いた要素より外にあるコンポーネントのVNodeが更新された時」であるためです。

つまり、カスタムディレクティブをひも付けた要素より外のコンポーネントのVNodeが変更されて、内側のコンポーネントのVNodeが変更されるのを待たずにupdateフックの処理が実行されてしまったということです。

まとめ

今回わかったこと

  • 複数のDOMに共通した操作をさせるために、自分で作れるカスタムディレクティブというものがある
  • カスタムディレクティブを定義するには、Vue.directive APIを使用する
  • updateフックとcomponentUpdateフックの違いは、カスタムディレクティブひも付けた要素より内側のコンポーネントのVNodeが変更される前か、変更された後かという部分

コメント

タイトルとURLをコピーしました