Others

その他の機能

前述してきたのはVue.jsのごく基本的な内容で、その他にも機能は細かく色々ある。

トランジション

Vue.jsでは、要素が追加、更新、削除された際にアニメーション効果を簡単に付与できる機能がある。基本的には、アニメーションを付けたい要素を<transition>タグで囲むだけでトランジション用のクラスが自動で追加される。追加されるクラスは、指定した要素が追加されるときは「enter」、削除されるときは「leave」という文字を含みそれぞれ開始と終了、トランジションがアクティブ状態のタイミングで適用される計6つ。下記のクラスは<transition>に名前(name属性)がない場合はデフォルトで接頭辞が「v-」となり、例えば名前が「fade」だった場合は「fade-*」となる。

自動で付与されるトランジション用デフォルトクラス
v-enter 対象要素がDOMに追加される前に適用され、トランジション(アニメーション効果)の開始直後に削除される。トランジションの開始時の状態(標準はopacity: 0)を指定。Vue 3では「v-enter-from」に名称変更。
v-enter-active 要素がDOMに追加されるとほぼ同時に追加されトランジションの終了時に削除される。トランジションの期間、遅延、イージングを定義する。
v-enter-to 要素がDOMに追加されるのとほぼ同時に追加され(同時にv-enterが削除)、トランジション終了時に削除される。
v-leave トランジションの開始直前に追加され、直後に削除される。要素が削除される前、トランジションの開始時の状態(標準はopacity: 1)を指定。Vue 3では「v-leave-from」に名称変更。
v-leave-active トランジション開始直前に追加され終了時に削除される。トランジションの期間、遅延、イージングを定義する。
v-leave-to トランジション開始直前に追加され(同時にv-leaveが削除)、トランジション終了時に削除される。
// 親テンプレート
<button v-on:click="isShow = !isShow">表示・非表示切り替え</button>
<transition>
  <div v-if="isShow">トランジションしたい要素</div>
</transition>
---------------------------
// 親コンポーネント
new Vue({
...
  data: {
    isShow: true
  }
...
---------------------------
// css
.v-enter,
.v-leave-to {
  opacity: 0;
}
.v-enter-active,
.v-leave-active {
  transition: opacity 1s ease;
}
.v-enter-to,
.v-leave {
  opacity: 1;
}
トランジションしたい要素

今のところ、トランジションについてはそんなに使う機会があるかなあといった感じ。追々必要になったところで公式を参考に掘り下げたい。

動的引数

v-bind、v-on、v-slotディレクティブの引数にあたる部分は[角括弧]で囲むとJavaScriptの式として動的に評価され、そこで評価された値が最終的な引数を指す値として使用される。これを動的引数と呼ぶ。
下記の例では、「attributeName」は「href」に置き換えられ「v-bind:href」と同じとなるのだが、現状わざわざそんな書き方をする理由がわからないので、使いどころが不明。ついでに「JavaScriptの式として動的に評価される」という意味も理解できず。とりあえず、こういった書き方もある程度に…。

<a v-bind:[attributeName]="url">リンク</a>
<a v-on:[eventName]="doSomething">click</a>
---------------------------
new Vue({
  el: '#App',
  data: {
    attributeName: 'href',
    eventName: 'click',
    url: 'https://jp.vuejs.org/v2/guide/syntax.html'
  }
})

動的コンポーネント

Vue独自タグの<component>にis属性を使うことで、ユーザーの操作などによって複数のコンポーネントを動的に切り替えることができる。

// 子コンポーネント
const tabContents1 = {
  template: `
  <div class="tabContainer">
    <p>タブコンポーネントのコンテンツ1</p>
  </div>
  `
};
const tabContents2 = {
  template: `
  <div class="tabContainer">
    <p>タブコンポーネントのコンテンツ2</p>
    <label><input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)">{{checked}}</label>
  </div>
  `,
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
};
const tabContents3 = {
  template: `
  <div class="tabContainer">
    <p>タブコンポーネントのコンテンツ3</p>
    <button @click="show = !show">クリック@{{show}}</button>
    <template v-if="show">
      <p>表示がアクティブのときだけ表示する。</p>
    </div>
  </div>
  `,
  data() {
    return {
      show: false
    }
  }
};
---------------------------
// 親テンプレート
<button v-for="tab in tabs" v-bind:key="tab.key" v-bind:class="['tabMenu', { 'is-active': currentTab === tab.key }]" v-on:click="currentTab = tab.key">{{ tab.name }}</button>

<component v-bind:is="tabComponents" v-model="testCheckbox"></component>
---------------------------
// 親コンポーネント
new Vue({
...
  components: {
    'tab-contents1': tabContents1,
    'tab-contents2': tabContents2,
    'tab-contents3': tabContents3,
  },
  data: {
    currentTab: 'contents1',
    tabs: [
      {
        key: 'contents1',
        name: 'タブ1'
      },
      {
        key: 'contents2',
        name: 'タブ2'
      },
      {
        key: 'contents3',
        name: 'タブ3'
      }
    ],
    testCheckbox: false
  },
  computed: {
    tabComponents() {
      return 'tab-' + this.currentTab.toLowerCase()
    }
  }
...

上記のタブ2の持つチェックボックスは子から親にチェックボックスのvalueの値を渡しているので、タブが切り替えられても状態が保持される。タブ3内のテキストの表示切り替えは、子コンポーネント内で処理されているため、一度テキストを表示したあとタブの表示を切り替えると初期状態に戻る。これを回避したいときは、コンポーネントの状態を保持(キャッシュ)する<keep-alive>で<component>要素をラップすればよい。

<keep-alive>
  <component v-bind:is="tabComponents" v-model="testCheckbox"></component>
</keep-alive>

Mixin

複数のコンポーネントで同一の機能(data、methods、computedなどのオプションやライフサイクルフック)を持たせたいとき、Mixinを利用することで機能を共通化できる。

// Mixinオブジェクト
const myMixin = {
  data(){
    return {
      mixinMsg: '「ミックスインで持つデータ」',
      btnTxt: 'ミックスインのメソッド'
    }
  },
  created: function () {
    console.log('ミックスインのcreated');
  },
  methods: {
    changeMsg: function () {
      this.mixinMsg = 'ミックスインでデータを変更';
    }
  }
}
---------------------------
// 子コンポーネント
Vue.component('mixin-component-01', {
  mixins: [myMixin],
  template: `
    <div class="u-mb1">
      <p>{{mixinMsg}}</p>
      <button @click="changeMsg">{{btnTxt}}</button>
    </div>
  `,
  created: function () {
    console.log('コンポーネントのcreated');
  }
});
Vue.component('mixin-component-02', {
  mixins: [myMixin],
  template: `
    <div class="u-mb1">
      <p>{{addMsg}}</p>
      <button @click="changeMsg">{{btnTxt}}</button>
    </div>
  `,
  data(){
    return {
      compoMsg: 'にコンポーネントでテキストを追加',
      btnTxt: 'コンポーネントのメソッド'
    }
  },
  computed:{
    addMsg() {
      return this.mixinMsg + this.compoMsg;
    },
  },
  methods: {
    changeMsg: function () {
      this.mixinMsg = '「コンポーネントでデータを変更」';
      this.compoMsg = 'コンポーネントのメソッド';
    }
  }
});
---------------------------
// 親テンプレート
<mixin-component-01></mixin-component-01>
<mixin-component-02></mixin-component-02>

Mixinとコンポーネントでdata、methods、computedなどのプロパティ名が同じものだった場合はコンポーネント側が優先され、ライフサイクルフックが重複している場合は、Mixin→コンポーネントの順で両方実行される。Mixinはグローバルな使用も可能だが使う際には注意が必要ということで、多用は推奨されないらしい。詳しくは公式を参照。

DOM要素へのアクセス

特別な属性refとインスタンスプロパティ$refsを使うことで、特定の DOM 要素やコンポーネントのインスタンスを取得できる。

// 子コンポーネント
Vue.component('toggle-component', {
  template: `
    <div class="u-mb2" ref="target">
    // 対象の要素に任意の名前でref属性を付与
      <template v-if="isShow">
        <slot></slot>
      </template>
      <button @click="toggleEvent">{{btnTxt}}</button>
    </div>
  `,
  data() {
    return {
      btnTxt: '',
      isShow: false,
      target:''
    }
  },
  mounted: function () {
    this.changeTxt();
    this.target = this.$refs.target;
    // 要素へのアクセスはDOMの生成が完了しているタイミングで行う
  },
  methods: {
    toggleEvent(){
      this.isShow = !this.isShow;
      this.changeTxt();
      this.target.scrollIntoView();
    },
    changeTxt() {
      if (this.isShow) {
        this.btnTxt = 'コードを閉じる';
      } else {
        this.btnTxt = 'コードを見る';
      }
    }
  }
});
---------------------------
// 親テンプレート
<toggle-component>
  <pre v-pre>...省略...</pre>
</toggle-component>

カスタムディレクティブ

「v-**」の形でディレクティブを自作できる。カスタムディレクティブの定義方法はdirective関数を使ってグローバルに登録するか、コンポーネントのdirectiveオプションで登録するかの2通り。グローバルであれば全てのコンポーネントで使用できるのでこちらの方が利便性はありそう。まだ理解は足りていないので触りだけ。

// グローバルカスタムディレクティブ
Vue.directive('カスタムディレクティブ名', {
  フック関数: function (フック引数) {
    ...対象要素に対する処理
  }
})
// グローバルカスタムディレクティブ
directives: {
  カスタムディレクティブ名: {
    フック関数: function (フック引数) {
      ...対象要素に対する処理
    }
  }
}
フック関数(決まったタイミングに処理を割り込ませる)
bind ディレクティブが初めて要素と紐づいた時
inserted 紐づいている要素が親Nodeに挿入された時
update 紐づいた要素を含むコンポーネントのVNodeが更新された時(子コンポーネントが更新される前)
フック引数
el ディレクティブが紐づく要素
binding name(v-(接頭辞)なしのディレクティブ名)やvalue(ディレクティブに渡される値)などのプロパティを含むオブジェクト

フック関数と引数については、上記以外にもいくつかある。詳しくは公式を確認。

※VNodeについては保留で。

// グローバルカスタムディレクティブ
Vue.directive('custom-directive', {
  bind: function (el, binding) {
    console.group('bind');
    console.log(el);
    console.log(binding);
    el.style.backgroundColor = binding.value;
    el.style.padding = '5px';
    el.placeholder = 'コンソールも確認'
  }
})
---------------------------
// テンプレート
<input type="text" v-custom-directive="'#dbf4f4'">

テンプレート制御ディレクティブ

テンプレートやコンパイル制御のためのディレクティブ。

v-pre テンプレートのコンパイルをスキップする。マスタッシュ構文タグもそのまま表示される。
v-once 対象を一度だけ描画し、そのあとは静的なコンテンツとして扱われる。
v-text 要素内のテキストコンテンツがマスタッシュ構文のみであれば、代わりにv-textを使って記述できる。
v-html htmlタグをそのまま表示させる。主にAPIから取得したHTML文章を描画するときに使用される。
v-clock Vueインスタンスのコンパイル完了すると取り除かれるので、CSSと組み合わせてコンパイル前のテンプレートが画面に表示されるのを防ぐ。CSSで指定するときは、要素名[v-clock]とする。
<div id="App" class="testCloak" v-cloak>
  ...コンテンツ...
</div>
---------------------------
// CSS
.testCloak{
  animation: cloak-in 1s;
}
.testCloak[v-cloak]{
  opacity: 0;
}
@keyframes cloak-in{
  0% {opacity: 0;}
}

修飾子

ディレクティブを使用するときに追加できる処理(修飾子)のこと。

v-modelに追加できる修飾子
修飾子 処理
.lazy 入力されたタイミング(inputイベント)ではなく、入力が確定したタイミング(changeイベント)で値を反映させる。
.number 入力された値を数値に変換する。
.trim 入力された値の余計なスペースを削除する。
v-model.lazy="プロパティ名"

イベント修飾子

イベント修飾子はイベントの振る舞いを変更する修飾子でv-onと合わせて使用する。

修飾子 処理
stop event.stopPropagation()を呼ぶ(イベント伝播の停止)
prevent event.preventDefault()を呼ぶ(イベントの無効)
capture 親要素のイベントを子要素のそれより先に実行できる。
self イベントが発生した要素が自分自身のときにハンドラが呼び出される。
once 初回に一度だけ実行される。ネイティブDOM専用。
passive ハンドラがevent.preventDefault()を含んでいても、呼び出さない。モバイルでのスクロールイベントでかくつきを防ぐのに使われる。
native コンポーネントのルート要素にあるイベントを呼び出す。ただし$emitを使う方法が主流で、Vue 3では廃止されている。

使用方法などは公式を参考にする。

キー修飾子

特定のキーがクリックされたときイベントを発火したい場合にv-onに追加して利用する。

修飾子 処理
.enter エンターキーが押下されたとき。
.enter エンターキーが押下されたとき。
.left マウスの左ボタンがクリックされたとき。
.right マウスの右ボタンがクリックされたとき。
.middle マウス中央のボタンがクリックされたとき。

詳しくは公式を参考にする。