Vue.jsにおけるコンポーネントの概要と、親子構造におけるデータ通信等の説明をしてみます。
ヘッダーやフッター、そして検索フォームなどの共通するUIや機能をまとめ、アプリケーションを効率良く構築することを目指したのが「コンポーネント」と呼ばれる仕組みです。ヘッダーに検索フォームといったように、コンポーネントは別のコンポーネント内に配置することも可能です。
概要をつかむために、まずはCDNによる1ファイルで説明します。
コンポーネントの配置
コンポーネントは主に「グローバル配置」「ローカル配置」の2つに分けられます。
グローバル配置されたコンポーネントは、すべてのコンポーネント内で利用することができます。
グローバル配置ではVue.componentを利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <div id="app"> <my-component-name /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.6/dist/vue.js"></script> <script> Vue.component('my-component-name', { data: function () { return { message1: '本日は', message2: '晴天なり' } }, template: '<p>{{message1}}{{message2}}</p>' }) new Vue({ el: '#app' }) </script> </body> </html> |
17〜26行目
第1引数で「my-component-name」という名前のコンポーネントを登録しています。第2引数でtemplateオプション等を持つオブジェクトを設定します。このオブジェクト自体がコンポーネントとなります。上記では、dataオプションも設定していますが、他にもmethods やcomputed、後述するpropsなどがあります。
10行目
実際にコンポーネントのtemplateオプションで定義した内容が表示される部分です。このタグをカスタムタグと呼びます。
下記コードが「ローカル配置」となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <div id="app"> <my-component-name /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.6/dist/vue.js"></script> <script> let myComponentName = { data: function () { return { message1: '本日は', message2: '晴天なり' } }, template: '<p>{{message1}}{{message2}}</p>' } new Vue({ el: '#app', components: { 'my-component-name': myComponentName } }) </script> </body> </html> |
28〜30行目
グローバル配置と違い、作成したオブジェクトは利用先のcomponentsオプションで定義する必要があります。上記では利用先である<div id=”app”>〜</div>内(elオプションが#appを設定)でのみ有効となるローカル配置となります。
後述しますが、上記においてルートVueインスタンスを親、myComponentNameは子の関係にあると言います。
コンポーネントの親子関係
ある任意のコンポーネントを取り込み利用した場合に、そのコンポーネント自体を子、利用した先は親という関係になります。この関係を端的に表したのが下図です。AコンポーネントはBコンポーネントを利用しています。
上記の関係を、コードで表現すると下記のようになります。23行目において、Aコンポーネントのtemplateの中で、子となるBコンポーネント(<b-component />)を利用しています。Bコンポーネントを利用するために、25行目においてbComponentを定義しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <div id="app"> <a-component /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.6/dist/vue.js"></script> <script> //Bコンポーネント let bComponent = { template: '<div>Bコンポーネント</div>', } //Aコンポーネント let aComponent = { template: '<b-component />', components: { 'b-component': bComponent } }; new Vue({ el: '#app', components: { 'a-component': aComponent } }) </script> </body> </html> |
30〜33行目
「コンポーネントの配置」の項目でも述べましたが、上記コードにおいて、ルートVueインスタンスはAコンポーネントの親という関係も成り立ちます。
コンポーネント間の通信、親から子へ(props)
次にコンポーネント間においてのデータのやり取りを説明していきます。
親コンポーネントから子コンポーネントへは、propsを利用してデータを渡します。
上述した親子関係を持つAコンポーネント(親)とBコンポーネント(子)において、実際に親が持つ「message: ‘こんにちわ’」というデータを子へ渡してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <div id="app"> <a-component /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.6/dist/vue.js"></script> <script> //Bコンポーネント let bComponent = { template: '<div>Bコンポーネント{{myMessage}}</div>', props: ['myMessage'] } //Aコンポーネント let aComponent = { template: '<b-component v-bind:my-message="message"/>', components: { 'b-component': bComponent }, data: function () { return { message: 'こんにちわ' } } }; new Vue({ el: '#app', components: { 'a-component': aComponent } }) </script> </body> </html> |
以下流れに沿って説明していきます。
30行目 親であるAコンポーネントの持つ「message」データです。
↓
24行目 子コンポーネント(b-component)へは属性で渡します。データバインディングで設定します。
↓
19行目 子であるBコンポーネントでは、親から受け取るデータはpropsオプション※1内で定義する必要があります。そしてテンプレート内(18行目)において{{myMessage}}のように記述し表示させます。
※1 propsについて:実際には配列ではなくオブジェクトにして、下記のように受け取る型を指定するなどの設定をおこないます。本記事では説明しません。
1 2 3 |
props: { myMessage: String } |
子から親へ($emitとカスタムイベント)
同様に、親子関係を持つAコンポーネント(親)とBコンポーネント(子)において、子コンポーネントで発生するイベントから親コンポーネント側のメソッドを実行してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <div id="app"> <a-component /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.6/dist/vue.js"></script> <script> //Bコンポーネント let bComponent = { //クリックするとbMethodが実行されカスタムイベントb-eventが発火する template: '<button @click="bMethod">親のメソッドを呼び出す</button>', methods: { bMethod: function () { this.$emit('b-event');//カスタムイベントの発火 } } } //Aコンポーネント let aComponent = { //カスタムイベントであるb-eventが発火されると、それをキャッチしてaMethodが実行される template: '<b-component @b-event="aMethod"/>', components: { 'b-component': bComponent }, methods: { aMethod: function () { alert('こんにちわ'); } } }; new Vue({ el: '#app', components: { 'a-component': aComponent } }) </script> </body> </html> |
19行目
@clickは、v-on:clickと同じです。子コンポーネント側のボタンをクリックすると、this.$emit(‘b-event’)でカスタムイベントであるb-eventが発火し、親側でそのb-eventをキャッチします(30行目)。
つまり、子コンポーネント側で$emit(‘カスタムイベント’)が発火すると、親コンポーネント側では「@カスタムイベント」で発火をキャッチ、それに紐付いたメソッドが実行される仕組みです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <div id="app"> <a-component /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.6/dist/vue.js"></script> <script> //Bコンポーネント let bComponent = { template: ` <div> <input type="text" v-model ="textData"/> <button @click="bMethod">親へ送る</button> </div>`, data: function () { return { textData: 'こんにちわ' } }, methods: { bMethod: function () { //自身が持つデータをオブジェクトで渡す this.$emit('b-event', { message: this.textData }); } } } //Aコンポーネント let aComponent = { template: '<b-component @b-event="aMethod"/>', components: { 'b-component': bComponent }, methods: { aMethod: function (payload) { alert(payload.message); } } }; new Vue({ el: '#app', components: { 'a-component': aComponent } }) </script> </body> </html> |
実際のプロジェクトにおいて
上記までのコンポーネントの仕組みをより具体的にイメージし理解するために、Vue Cli 3によって生成された実際のプロジェクトで見てみます。
プロジェクトの作成に関しては下記の関連ページをご覧下さい。
関連ページ
Vue CLI 3 ではじめるVue RouterとVuex入門
下記構造がVue Cli 3によって生成されたデフォルトのファイル群となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
. ├── README.md ├── babel.config.js ├── node_modules ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── favicon.ico │ └── index.html └── src ├── App.vue ├── assets │ └── logo.png ├── components │ └── HelloWorld.vue ├── main.js ├── router.js ├── store.js └── views ├── About.vue └── Home.vue |
src>componentsディレクトリ内にHelloWorld.vueがありますが、これがいわば部品として利用されているコンポーネントです。実際にはHome.vue内で利用されています。
Home.vueファイルは下記のように記述されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template> <script> // @ is an alias to /src import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script> |
10行目でHelloWorld.vueを取り込み、このコンポーネントを利用するために自身のcomponentsオプション(15行目)で定義しています。実際にHTMLとして描画されるtemplate内において<HelloWorld />タグを記述し、子コンポーネントであるHelloWorld.vue(のtemplate)を表示させています(4行目)。
<HelloWorld />タグにはその属性として、msg=”Welcome to Your Vue.js App”が指定されています。これは子コンポーネントに対してデータを送っています。
下記コードが子であるHelloWorld.vueですが、10〜12行目のpropsオプションにおいて、親から渡されるmsgを定義しています。実際にHTMLで表示されるのは3行目の{{ msg }}です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<template> <div class="hello"> <h1>{{ msg }}</h1> </div> </template> <script> export default { name: 'HelloWorld', props: { msg: String } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style> |
参照ページ