Vue のカスタムディレクティブを使って低レベルの DOM にアクセスする

Vue.js には多くの便利なディレクティブが用意されており、多くの場合はこれらを組み合わることでコンポーネントを作成することができるようになっています。

しかし、以下の場合のように低レベルの DOM に対して処理を追加したい場合があります。

  • コンポーネントの innerHTML:thinking-face: が含まれていたら 🤔 を表示する
  • コンポーネントが親 Node に挿入されたときにフォーカスを当てたい

このような場合には、カスタムディレクティブを使うことで対応することが可能です。

ここでは公式ドキュメントのカスタムディレクティブのページ参考にしつつ、自分なりに試したことをメモ書きにしています。

カスタムディレクティブ - Vue.js

では、上で挙げた

コンポーネントの innerHTML:thinking-face: が含まれていたら 🤔 を表示する

をカスタムディレクティブを使って実装する例を見てみましょう。

    export default {
      name: "HelloWorld",
      props: {
        msg: String
      },
      directives: {
        emoji: {
          bind(el) {
            el.innerHTML = el.innerHTML.replace(/:thinking-face:/gi, "🤔");
          }
        }
      }
    };

上記の例ではコンポーネントの directives へカスタムディレクティブ emoji を定義しています。

directives で定義したプロパティはカスタムディレクティブ名となり、 v- + directivesで登録したプロパティ名 で使うことができるようになります。

<p v-emoji>楽しいインターネット :thinking-face:</p>

フック関数を使って DOM にアクセスする

コンポーネントの HTML からカスタムディレクティブを利用できるようになったところで、次はカスタムディレクティブを利用した要素の DOM に対して処理を書いていきます。

先ほどのコードを見てみると、カスタムディレクティブ emoji の中には bind という関数が定義されていました。

    emoji: {
      bind(el) {
        el.innerHTML = el.innerHTML.replace(/:thinking-face:/gi, "🤔");
      }
    }

これは、フック関数と呼ばれるもので、フック関数を定義することでディレクティブが特定の状態になったとき*1に実行する処理を追加することができます。

初めて対象の要素にひも付いた時、親 Node に挿入された時、ひも付いた要素を抱合しているコンポーネントの VNode が更新されるときなど

他のフック関数に付いては以下を参照

https://jp.vuejs.org/v2/guide/custom-directive.html#フック関数

ここで利用している bind についての説明を見てみると、

bind: ディレクティブが初めて対象の要素にひも付いた時に 1 度だけ呼ばれます。ここで 1 回だけ実行するセットアップ処理を行えます。

とありますので、ディレクティブがひも付く最初の1度目に発火する処理を定義しておくことができます。

ではもしこのコンポーネントに更新が入る場合があり、その際も同じ処理を適用したい場合はどうしたらよいでしょうか?

この場合はフック関数 update を定義します。

update: ひも付いた要素を抱合しているコンポーネントの VNode が更新される度に呼ばれます。しかし、おそらく子コンポーネントが更新される前でしょう。 ディレクティブの値が変化してもしなくても、バインディングされている値と以前の値との比較によって不要な更新を回避することができます。(フック引数に関しては下記を参照してください)

    emoji: {
      bind(el) {
        el.innerHTML = el.innerHTML.replace(/:thinking-face:/gi, "🤔");
      },
      
      update(el) {
        el.innerHTML = el.innerHTML.replace(/:thinking-face:/gi, "🤔");
      }
    }

重複した bind, update の処理を省略記法でまとめる

同じ処理が2回記述されているので、共通の処理を外に定義し直したいですね。

多くの場合は bind と update に同じ振る舞いを書くだけのケースで解決できるため、省略記法を使ってまとめてひも付ける処理を書くことができるようになっています。

    emoji(el) {
      el.innerHTML = el.innerHTML.replace(/:thinking-face:/gi, "🤔");
    }

Demo