本文書にたどり着いた人はVueのプラグインを知りたいので公式のドキュメント見たけど理解できなかったまたはVue.use()メソッドとプラグインとの関係を知りたいという人ではないでしょうか。本文書ではドキュメントを参考にプラグインの作成の基礎の説明を行い、基礎を理解した後により実践的なドロップダウンメニューのプラグインの作成を行います。読み終えたころにはオリジナルのプラグインの作成方法とプラグインの追加方法を理解しているはずです。

動作確認はVue3を利用しています。

一番簡単なプラグインを作成してみよう

早速最も簡単なプラグインを作成してみましょう。Vue CLIを利用してプロジェクトの作成を行います。presetではVue 3 Previewを選択しています。


 % vue create vue-plugin-test
//略
? Please pick a preset: 
  Default ([Vue 2] babel, eslint) 
❯ Default (Vue 3 Preview) ([Vue 3] babel, eslint) 
  Manually select features 

プラグインの作成

プロジェクトの作成後、srcフォルダにpluginsフォルダを作成しその下にFirstPlugin.jsファイルを作成します。

プラグインの作成はドキュメントに記述されている通りに行います。

Whenever this plugin is added to an application, the install method will be called if it is an object. If it is a function, the function itself will be called. In both cases, it will receive two parameters – the app object resulting from Vue’s createApp, and the options passed in by the user.

プラグインが追加された時、オブジェクトであればinstallメソッドが実行され、関数であればその関数が実行されると記述されているので2つの方法でプラグインを作成してみましょう。

オブジェクトの場合は下記のように記述することができます。installメソッドを含んでいます。


const FirstPlugin = {
  install() {
    console.log('Hello Plugin from Object');
  },
};
export default FirstPlugin;

関数の場合は下記のように記述することができます。こちらはinstallメソッドはいれません。関数の名前はどんな名前にしても実行されます。


const FirstPlugin = () => {
  console.log('Hello World from Function');
};

export default FirstPlugin;

プラグインの追加と実行方法

プラグインの使い方についてもドキュメントの記述されている通り実行してみましょう。

After a Vue app has been initialized with createApp(), you can add a plugin to your application by calling the use() method.

Vue appがcreateApp()で初期化された後use()メソッドでアプリケーションにプラグインを追加することができると記述されています。

main.jsファイルを開いて記述通りに設定を行います。追加するプラグインをimportしてapp.use()メソッドの指定します。


import { createApp } from 'vue';
import App from './App.vue';
import FirstPlugin from './plugins/FirstPlugin';

const app = createApp(App);

app.use(FirstPlugin);

app.mount('#app');

設定が完了したらnpm run serveコマンドで開発サーバを起動してブラウザでアクセスしてデベロッパーツールでコンソールを確認してください。関数を利用して実行したので”Hello World from Function”が表示されます。オブジェクトを利用した場合は”Hello World from Object”が表示されます。

プラグインで実行したメッセージ表示
プラグインで実行したメッセージ表示

プラグインを作成したことがない人にとってはプラグインの作成と追加はこんなに簡単なのかと思ったのではないでしょうか。

main.jsではFirstPluginをimportしていますが、importしなくても直接main.jsにFirstPluginのオブジェクトまたは関数を記述しても動作します。


import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

const FirstPlugin = {
  install() {
    console.log('Hello Plugin from Object');
  },
};

app.use(FirstPlugin, 'オプション');

app.mount('#app');

パラメータの確認

プラグインは以下のドキュメントの説明通り2つのパラメータを受け取ることができるのでこちらも動作確認してみます。

it will receive two parameters – the app object resulting from Vue’s createApp, and the options passed in by the user.

1つはcreateAppを実行した後に作成されるオブジェクト、1つのはユーザによって渡されるオプションと記述されています。

まずは一つ目の引数に入るappオブジェクトの確認です。オブジェクトでも関数でも結果は同じなので好きな方で確認してください。


//オブジェクトの場合
const FirstPlugin = {
  install(app) {
    console.log(app);
    console.log('Hello Plugin from Object');
  },
};

//関数の場合
// const FirstPlugin = () => {
//   console.log(app);
//   console.log('Hello World from Function');
// };

export default FirstPlugin;

Vueの情報が確認できます。component, directive, mixin, useなどの関数を確認することができます。これらの関数はプラグインの中で利用することができます。本文書では後ほどcomponentを利用します。

パラメータのappを確認
パラメータのappを確認

もしVueのバージョンが知りたい場合はapp.versionでアクセスすることができます。バージョン情報を利用することで稼働するバージョンによって異なる処理を行うといったようなことが可能になります。


const FirstPlugin = {
  install(app) {
    console.log(app.version);
    console.log('Hello Plugin from Object');
  },
};

コンソールには”3.1.1”が表示されます。

次に2つ目のパラメータoptionsを確認しましょう。引数にoptionsを追加します。


//オブジェクトの場合
const FirstPlugin = {
  install(app, options) {
    console.log(options);
    console.log(app);
    console.log('Hello Plugin from Object');
  },
};

//関数の場合
// const FirstPlugin = (app, options) => {
//   console.log(options);
//   console.log(app);
//   console.log('Hello World from Function');
// };

export default FirstPlugin;

optionsが設定されていないのでコンソール上には”undefined”と表示されます。

次にapp.user()メソッドのプラグイン名の後に文字列を追加します。


app.use(FirstPlugin, 'オプション');

再度コンソールを確認すると渡された文字オプションが表示されていることが確認できます。文字列以外のオブジェクト等も渡すことができます。

optionsで渡された内容を表示
optionsで渡された内容を表示

最も簡単なプラグインを作成を通してプラグインを作成するための基礎を理解することができました。

プラグイン作成の実践編

プラグインの基礎がわかったけどより実践的なコードで記述されたプラグインにはどのようなものがあるか知りたいという人もいるかと思います。そのような場合はvue プラグイン おすすめ等で検索してみてください。さまざまなプラグインが公開されています。どれか一つを選択してGitHubに行くと設定方法でVue.useメソッドを使っていることまたソースコードの中でinstallメソッドを利用していることが確認できるかと思います。

本文書ではオープンソースのプラグインとして公開されているDropDwonMenuをよりシンプルにしたプラグインを作成します。作成方法を理解できれば自作のプラグイン作成も敷居がぐっと下がるはずです。

Vue.componentメソッドの利用方法

先ほどはプラグインを作成したといってもコンソールにメッセージを表示させるだけのものでした。ここではVue.component()メソッドをinstallメソッド内で利用することでVue全体で利用できるコンポーネントを作成します。

pluginsフォルダに中にSecondPlugin.jsファイルを作成します。app.componentでコンポーネントを追加することができます。もっとapp.componentを知りたいという場合はVue.jsのドキュメンを確認してください。


import DropDownMenu from './DropDownMenu';

const SecondPlugin = {
  install(app) {
    app.component('DropDownMenu', DropDownMenu);
  },
};

export default SecondPlugin;

componentの第一引数はコンポーネント名で第二引数にはvueファイルを指定しています。コンポーネント名はvueで利用する際にタグ名として利用します。指定したvueファイルであるDropDwonMenu.vueファイルをpluginフォルダに作成してください。中身は通常のvueファイルです。


<template>
  <div>Hello SecondPlugin</div>
</template>
<script>
export default {};
</script>

main.jsではVue.use()メソッドで作成したSecondPluginを追加します。


import { createApp } from 'vue';
import App from './App.vue';
import SecondPlugin from './plugins/SecondPlugin';

const app = createApp(App);

app.use(SecondPlugin);

app.mount('#app');

これでコンポーネントを追加できるプラグインの設定は完了です。App.vueファイルの中で追加したコンポーネントを利用してみましょう。


<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <drop-down-menu />
</template>

<script>
export default {
  name: 'App',
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

ブラウザで確認するとDropDownMenu.vueファイルのtemplateタグの中で記述したSecondPluginが表示されます。Pluginを利用してコンポーネントを追加する方法も確認できました。

Plugin内で追加したコンポーネント表示
Plugin内で追加したコンポーネント表示

ドロップダウンメニュープラグインの作成

ここからはDropDownMenu.vueファイルでプラグインの中身を更新しながらApp.vueからslotまたはpropsで渡す値を設定していきます。

Slotの設定

汎用的にするためにボタンやメニューはプラグインではなくslotを使って外側(親コンポーネント)から設定を行います。DropDownMenu.vueファイルでは2つのslotを設定します。2つのSlotを利用しているので片方のSlotにはdropdownという名前をつけています。デフォルトのslotには名前をつけていません。


<template>
  <div class="dropdown">
    <slot></slot>
    <div>
      <slot name="dropdown"></slot>
    </div>
  </div>
</template>
<script>
export default {};
</script>
<style>
.dropdown {
  display: inline-block;
}
</style>

DropDownMenuコンポーネントを利用するApp.vueからSlotの中身を設定します。デフォルトのSlotにはボタンの要素を渡しています。dropdownのSlotには3つのメニューを入れています。またメニューにはdropdown-itemクラスを設定しています。


<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <div>
    <drop-down-menu v-model="show" :closeOnClickOutside="closeOnClickOutside">
      <button>ドロップダウンメニュー</button>
      <template v-slot:dropdown>
        <a class="dropdown-item" href="#">Vue.js</a>
        <a class="dropdown-item" href="#">React</a>
        <a class="dropdown-item" href="#">Svelte</a>
      </template>
    </drop-down-menu>
  </div>
</template>

<script>
export default {
  name: 'App',
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.dropdown-item {
  display: block;
  padding: 0.25rem 1.5rem;
  font-weight: 400;
  color: #212529;
  text-decoration: none;
}
</style>

ブラウザにはボタンと3つのメニューが表示されます。

Slotによるボタンとメニューの設定
Slotによるボタンとメニューの設定

表示・非表示の制御

ドロップダウンメニューの開閉を制御するデータプロパティshowをApp.vueファイルで設定します。


<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <div>
    <drop-down-menu v-model="show">
      <button>ドロップダウンメニュー</button>
      <template v-slot:dropdown>
        <a class="dropdown-item" href="#">Vue.js</a>
        <a class="dropdown-item" href="#">React</a>
        <a class="dropdown-item" href="#">Svelte</a>
      </template>
    </drop-down-menu>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      show: false,
    };
  },
};
</script>

v-modelを設定しているのでDropDownMenuコンポーネント側ではpropsのmodalValueとして値を受け取ることができます。コンポーネントにv-modelを設定した場合のpropsのmodalValueについてはhttps://v3.vuejs.org/guide/component-basics.html#using-v-model-on-componentsで確認することができます。

受け取ったmodalVauleをv-showで利用します。App.vueで設定したデフォルト値はfalseになります。


<template>
  <div class="dropdown">
    <slot></slot>
    <div v-show="modelValue">
      <slot name="dropdown"></slot>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    modelValue: {
      type: Boolean,
    },
  },
};
</script>

v-showがfalseなのでブラウザで確認するとメニューが非表示になります。

メニューが非表示
メニューが非表示

表示と非表示を切り替えれるようにクリックイベントを設定します。クリックイベントで設定したtoggleMenuメソッドではemitにupdate:modalValueイベントを設定することでthis.modalValueがfalseの場合はtrue, trueの場合はfalseを親コンポーネントに伝えます。親コンポーネントであるApp.vueファイル側ではemitのイベントを受け取ることでshowの値がボタンをクリックするとtrue,falseと切り替わります。


<template>
  <div class="dropdown" @click="toggleMenu">
    <slot></slot>
    <div v-show="modelValue">
      <slot name="dropdown"></slot>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    modelValue: {
      type: Boolean,
    },
  },
  emits: ['update:modelValue'],
  methods: {
    toggleMenu() {
      this.$emit('update:modelValue', !this.modelValue);
    },
  },
};
</script>
<style>
.dropdown {
  display: inline-block;
}
</style>

ブラウザで確認するとボタンを押すとことで表示・非表示を繰り返すドロップダウンメニューを作成することができます。

メニューの外側でのクリックの制御

ドロップダウンメニューの外側をクリックした時にメニューを閉じる機能を追加します。この機能は利用するしないを親コンポーネント側で指定できるようにデータプロパティcloseOnClickOutsideを追加し、propsで渡します。


<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <div>
    <drop-down-menu v-model="show" :closeOnClickOutside="closeOnClickOutside">
      <button>ドロップダウンメニュー</button>
      <template v-slot:dropdown>
        <a class="dropdown-item" href="#">Vue.js</a>
        <a class="dropdown-item" href="#">React</a>
        <a class="dropdown-item" href="#">Svelte</a>
      </template>
    </drop-down-menu>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      show: false,
      closeOnClickOutside: true,
    };
  },
};
</script>

渡されたcloseOnClickOutsideを受け取れるようにpropsの設定を行います。


props: {
  modelValue: {
    type: Boolean,
  },
  closeOnClickOutside: {
    type: Boolean,
    default: true,
  },
},

DropDownMenuではイベントリスナーを設定することでクリックイベントを検知してメニューの外側でクリックされてかどうか判断します。イベントリスナーをライフサイクルフックのmountedで追加しますが、イベントリスナーを追加する場合は忘れずに削除処理を追加する必要があります。削除処理はライフサイクルフックのdestroyedで行います。propsで渡されるcloseOnClickOutsideがtureの場合のみイベントリスナーの追加・削除処理を実行します。


mounted() {
  if (this.closeOnClickOutside) {
    document.addEventListener('click', this.clickOutside);
  }
},
destoryed() {
  if (this.closeOnClickOutside) {
    document.removeEventListener('click', this.clickOutside);
  }
},

再度にclickOutsideメソッドを追加します。$eventではクリックした要素を取得することができるのでクリックした要素がDropDownMenuの要素(this.$el)に含まれているかチェックを行い、含まれていないのみメニューを閉じる処理を行なっています。


methods: {
  toggleMenu() {
    this.$emit('update:modelValue', !this.modelValue);
  },
  clickOutside($event) {
    if (!this.$el.contains($event.target)) {
      if (this.modelValue && this.closeOnClickOutside) {
        this.$emit('update:modelValue', false);
      }
    }
  },
},

これで設定は完了です。App.vueでcloseOnClickOutsideの値をtrueにした場合にメニューを開いた後、ドロップダウンメニューの外側の要素をクリックすることメニューが非表示になることを確認してください。またfalseにした場合は外側をクリックしてもメニューが閉じないことも確認してください。プラグインってこんなものなのと思っている人もいるかもしれませんが実際に公開されスターがついているプラグインでも思ったよりもシンプルなものもあります。

本文書を読み始めた時にVue.jsのプラグインの設定方法、使い方がわからなかった人も本文書の手順を確認してプラグインの理解を深まったのではないでしょうか。ぜひオリジナルのプラグインを作成にチャレンジしてみてください。