Vueで開発をしている際に同一の記述や似たような記述を繰り返す部分をComponent(コンポーネント)としてまとめて記述しておき、htmlタグと同じような扱いで再利用しやすくしることができます。本記事ではこのComponentについて基本的にな書き方や、使い方、実装例を紹介します。さらに、後半では発展的な内容である、propsやemitなどについても解説します。
VueのComponent(コンポーネント)とは?
Vueにはいくつかのhtmlタグが集合しているものをコンポーネントとして再利用しやすい形式でまとめることができます。コンポーネントとして登録されたものはhtmlタグのような形式で簡単に再利用ができます。例えば、ELOOPの開発課題一覧画面において、それぞれの開発課題のカードをコンポーネントとしてまとめて再利用することができます。このようにwebページの要素をコンポーネントで区切って開発することでページの可読性が上がり、コードを見返す際や他人が読む際に便利になります。また、コードの拡張性も上がり、レイアウトの変更や追加が生じた際にも少ない手順でこれらの作業ができるようになります。
VueのComponentの使い方
それではVueのComponent(コンポーネント)の書き方について説明していきます
VueのComponentの書き方
componentは次のような方法で登録(グローバル登録)できます。
(app.js)
Vue.component('コンポーネント名', { ここにコンポーネントの情報を記述 }) new Vue({ //Vueインスタンスを作成 el: '#app' })
グローバル登録されたコンポーネントは次のような記述で利用できます。
(index.html)
<body> <div id='app'> <コンポーネント名></コンポーネント名> </div> <script src='./app.js'></script> </body>
上記のようにHTMLタグのようにコンポーネント名を記述することでコンポーネントを利用できます。ただし、Vueインスタンスが反映されている要素内部(上記ではid=’app’のdivタグ内部)でしかグローバル登録されたコンポーネントは使用できません。
VueのComponentの実装例
それでは実際に、コンポーネントを利用して「hello world!」と画面に出力してみましょう。
(app.js)
Vue.component('sample', { //sampleでコンポーネント名を定義 data() { //data()でdataを定義 return { msg: 'hello ELOOP!' } }, template: '<div>{{ msg }}</div>' //templateで内包するHTML要素を定義 }) new Vue({ //Vueインスタンスを作成 el: '#app' })
上記のようにsampleという名前でコンポーネントを登録(グローバル登録)したのち、sampleコンポーネントにはmsgというdataを持たせます。msgはtemplateプロパティ内でdivタグ内部に出力されるようになっています。dataはコンポーネントごとに独立のものでなくてはならないため、関数で記述します。
(index.html)
<body> <div id='app'> <sample></sample> </div> <script src='./app.js'></script> </body>
sampleコンポーネントをdivタグ内に記述して呼び出します。
画面表示は以下のようになります。
しっかり文字が出力されていることが確認できます。
デベロッパーツール上でソースファイルを確認すると表示されているHTML要素は以下のようになっていることがわかります。
グローバルコンポーネントとローカルコンポーネントの違い
Vueのコンポーネントにはグローバルコンポーネントとローカルコンポーネントの二種類に分類されます。グローバルコンポーネントは登録後に読み込まれた全てのVueインスタンス内で使用できる一方で、使用しない場合であってもVueを通じた最終的なビルド時にコンポーネントが含まれてしまうため、閲覧時に読み込むJavaScriptファイルのサイズが大きくなってしまう場合があります。これを避けるためにローカルコンポーネントが提供されています。本項では、それぞれの書き方について説明していきます。
グローバルコンポーネントについて
グローバルコンポーネントは前項で紹介している書き方と同様ですので割愛しますがJavaScriptは上から順番に読み込んでしまうため次のようなコードには注意が必要です。
(app.js)
Vue.component('sample1', { data() { return { msg: 'hello ELOOP!' } }, template: '<div>{{ msg }}</div>' }) new Vue({ el: '#app' }) Vue.component('sample2', { data() { return { msg: 'hello ELOOP!' } }, template: '<div>{{ msg }}</div>' })
上記のような場合、sample1のコンポーネントは読み込まれますが、sample2のコンポーネントはVueインスタンスが生成されたのちに定義されているので、Vueインスタンス内部では使用できないことに注意してください。
ローカルコンポーネントについて
ローカルコンポーネントは登録時の記述方法と、使用する際に宣言が必要なことの二点がグローバルコンポーネントと異なります。
登録時の記述方法は次のようになります。
(app.js)
const Sample = { data() { return { msg: 'hello ELOOP!' } }, template: '<div>{{ msg }}</div>' }
このように、JavaScriptオブジェクトとして定義します。オブジェクト名をコンポーネント名として宣言します。
登録したコンポーネントは次のようにして利用します。
(app.js)
new Vue({ el: '#app', components: { 'sample': Sample } })
(index.html)
<body> <div id='app'> <sample></sample> </div> <script src='./app.js'></script> </body>
このように利用するrootインスタンスまたは、コンポーネントのcomponentsプロパティ内部で紐付けを行います。
Component同士でのデータのやり取り
コンポーネントを利用して開発をしていく中で、コンポーネント同士が階層構造になっていく場合があり、コンポーネント同士が親子関係で表現されることがあります。この際に、親子間のコンポーネント同士でデータの受け渡しを行うことがあります。本項では親から子へデータを渡すpropと子から親へイベントを渡すemitについて紹介します。
親から子へ(props)
親から子へはpropsによってデータを渡します。本記事では書き方のみ説明しますが、より詳細なオプションなどを知りたい方は「Vueのpropsの書き方・使い方について解説」を参照してみてください。
propsは次のように書きます。
(app.js)
Vue.component('sample', { props: ["msg"], template: '<div>{{ msg }}</div>' }) new Vue({ el: '#app', })
このようにして受け取りたいデータ(変数やオブジェクト)をpropsプロパティ内に記述します。
propsで渡すものは親となるコンポーネントまたはrootインスタンスで次のようにして指定します。
(index.html)
<body> <div id='app'> <sample msg="hello ELOOP!"></sample> </div> <script src='./component.js'></script> </body>
今回は静的データのpropsでの受け渡しについて紹介しましたが、動的なデータの受け渡しなどについては「Vueのpropsの書き方・使い方について解説」を参照してください!
子から親へ(emit)
子から親へはemitによってイベントを送ることができます。例えば子コンポーネントで発生したクリックイベントを親のコンポーネントに渡すことで親のmethodsプロパティ内で定義されたメソッドを実質的なハンドラメソッドとして実行することができます。
emitによるイベントの送出は次のように記述します。
(app.js)
Vue.component('sample', { methods: { alert: function () { alert("pushされました!") } }, template: '<push-button v-on:push=alert()></push-button>' }) Vue.component('push-button', { props: ["msg"], template: `<button v-on:click="$emit('push')">クリック</button>` }) new Vue({ el: '#app', })
上記のコードではsampleコンポーネントが親、push-buttonコンポーネントが子になっており、子から親へpushイベントを送出しています。(pushイベントはカスタムイベントのため、名前は自由に変更することができます。)
上記のように$emitでイベント名を子から親に送り、親では送られてきたイベントに対するハンドラを渡すことで、push-buttonコンポーネントのボタンを押すとsampleコンポーネントで定義されたalert()関数が実行されてアラートが表示されます。
コンポーネントでモーダルを作ってみる(実装例)
第4項までの知識を使って、モーダルを実装してみます。コードは次の通りです。
(index.html)
<!DOCTYPE html> <html lang='ja'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <meta http-equiv='X-UA-Compatible' content='ie=edge'> <link rel='stylesheet' href='./style.css'> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <title>モーダルのサンプル</title> </head> <body> <div id="app"> <button @click="isActive = true">モーダルを見る</button> <modal v-if="isActive" @close="isActive = false"> </modal> </div> <script src='./app.js'></script> </body> </html>
(app.js)
Vue.component('modal', { template: ` <div class="modal-mask"> <div class="modal-container" @click="$emit('close')"> モーダルが表示されます。クリックすると消えます。 </div> </div> ` }) new Vue({ el: '#app', data: { isActive: false } })
(style.css)
.modal-mask { position: fixed; z-index: 9998; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, .5); } .modal-container { width: 300px; margin: 20% auto; padding: 20px 30px; border-radius: 2px; background-color: #fff }
モーダルで表示される部分がコンポーネントになっています。ボタンをクリックすることでisActive変数が切り替わり、モーダルの表示と非表示が切り替わります。モーダル自体をクリックするとcloseイベントがrootインスタンスに送出されて、isActiveがfalseになりモーダルが閉じます。
非同期コンポーネントの使い方
APIの処理などでレンダリングに時間がかかるコンポーネントは非同期でレンダリングできるとユーザーにとって良いでしょう。このような場合を想定してVueでは非同期コンポーネントを用意しています。
非同期コンポーネントの書き方
非同期コンポーネントは以下のようにして実装することができます。
(app.js)
Vue.component('コンポート名', function (resolve, reject) { //非同期処理の内容 resolve({ //レンダリングさせるコンポーネントの内容 }) })
グローバルコンポーネントでの実装方法です。本来であればコンポート名を記述する第二引数にresolveを引数に持ったファクトリ関数を記述します。ファクトリ関数内ではresolve()内にコンポーネントを定義することができます。ローカルコンポーネントでも考え方は同じです。6.2項での実装例ではローカルコンポーネントを実装しているので、こちらも参考にしてみてください。
非同期コンポーネントの実装例
実際に非同期コンポーネントの実装例を紹介し、解説します。今回は以下のように個人情報を表示するアプリを実装例にします。
(index.html)
<!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <meta http-equiv='X-UA-Compatible' content='ie=edge'> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script> <title>Document</title> </head> <body> <div id='app'> <button v-on:click="isActive = !isActive">メンバーを見る</button> <div v-if='isActive'>メンバーを表示</div> <async-example v-if='isActive'></async-example> </div> <script src="./app.js"></script> </body> </html>
Index.htmlではクリックでisActiveのtrueとfalseの切り替えをできるボタンと非同期のコンポーネントが記述されています。headタグ内ではVueとAPIリクエストに用いるaxiosをCDNで読み込んでいます。
(app.js)
const nameCard = function (resolve) { axios.get('https://randomuser.me/api/?inc=gender,name,picture') .then(function (response) { resolve({ data: function () { return { name: response.data.results[0].name.first + ' ' + response.data.results[0].name.last, gender: response.data.results[0].gender, imgUrl: response.data.results[0].picture.large } }, template: ` <div> <p>Name : {{ name }}</p> <p>Gender : {{ gender }}</p> <img :src="imgUrl"/> </div> ` }) }) .catch(function (response) { resolve({ data() { return { msg: response } }, template: `<div>{{ msg }}</div>` }) }) } new Vue({ el: "#app", data: { isActive: false }, components: { 'async-example': nameCard, } })
app.jsでは非同期コンポーネントであるnameCardをローカルコンポーネントとして定義しています。axios.get()関数で「https://randomuser.me/api/?inc=gender,name,picture」からデータを取得し、取得に成功すると、resolve内で定義されたコンポーネントがcardNameとして宣言されます(.then内)。axios.get()関数で正常にデータを受け取れなかった場合はエラーメッセージをcardNameとして宣言します(.catch内)。これらにより、非同期でコンポーネントを実装することができます。また、非同期コンポーネントはキャッシュを利用しているため、一度レンダリングされたコンポーネントに関しては内容に変更がない限りキャッシュを利用して再描画を行います。したがって、ボタンの切り替えでカードを二度目以降に表示する際は描画の速度が通常のコンポーネントと同程度になります。
この記事のまとめ
本記事ではVueのComponentを、実際の実装例を元に説明しました!
最後に記事の要点をまとめてみましょう。
- Componentを登録して再利用することで可読性、拡張性の高いコードを記述できる
- dataは関数にして記述する
- グローバルコンポーネントとローカルコンポーネントがあり、用途に合わせた使い分けをできるといい
- 親から子へはpropsによってデータを渡す
- 子から親へはemitによってイベントを送る
- 非同期でレンダリングされるコンポーネントはファクトリ関数を用いて実装できる
VueのComponentを是非活用してみましょう!