はじめに
WEBサイトを見るとヘッダー・フッター・メイン・サイドバーなどいくつかのパーツからページが構成されている。これらのパーツを機能ごとに切り分けたものをコンポーネント
という。
コンポーネントは部品のようなもので、その部品を組み合わせることでページを構築していく。
ヘッダーやフッターなど複数のページで同じパーツを使いたい場合もコンポーネント化すれば必要に応じてそのコンポーネントを呼び出すだけでパーツが利用できる。
再利用可能で汎用性が高いというのがコンポーネントの大きなメリットである。
Vueにおけるコンポーネント
Vueではjavascriptとテンプレート(HTML)を1セットとして機能ごとに切り分けることができる。
サイトの規模が大きくなるほどHTML、CSS、javascriptのソースコードは管理しづらくなり、どこでどのソースコードが書かれているのか分かりづらくなるが、このコンポーネント機能を利用することでソースコードの管理がしやすくなる。
さらに、単一ファイルコンポーネントを使用すればHTML、CSS、javascriptをまとめて記述することができる。
コンポーネントの定義方法
コンポーネントはグローバル、またはローカルに登録し、カスタムタグとして使用する。
🌟 ポイント
コンポーネントはルートインスタンスが作成される前に定義しておく必要があるので、new Vue()する前
に定義する。
※ここでいうグローバルとローカルは
グローバル → 広域・全体
ローカル → 限られた場所
グローバルでのコンポーネントの登録
コンポーネントをグローバルに登録するにはVue.component
メソッドを使用する。
グローバルに登録することで全てのコンポーネント(ルートインスタンス)から使用できるようになる。
メソッドの引数には以下の情報を渡す↓
① カスタムタグに使用する名前(コンポーネントをHTMLで呼び出すときに使用)
② コンポーネントのオプションオブジェクト
↓基本的な書き方
Vue.component('コンポーネントの名前',{
template: 'HTMLに表示させたい内容'
})
↓実際に使用してみると
// my-componentがカスタムタグに使用する名前
Vue.component('my-component',{
// テンプレートオプションにページに表示させたい内容を記述
template: '<p>Hello World!</p>'
})
new Vue({
el: 'app'
})
↓コンポーネントを呼び出したい場所で先ほど定義したカスタムタグをHTMLタグのように記述。
<div id="app">
<!-- 実際に描画されると、この部分に「Hello World!」が表示される -->
<my-component></my-component>
</div>
ローカルでのコンポーネントの登録
定義したコンポーネントを任意のルートインスタンスのcomponentsオプション
に登録することでローカルとして登録され、特定のルートインスタンスのスコープ内だけでコンポーネントが使用できるようになる。
// コンポーネントを定義して変数myComponentに代入
var myComponent = {
template: '<p>Hello World!</p>'
}
new Vew ({
el: 'app01'
})
// コンポーネントはapp02のみで使用できる
new Vew ({
el: 'app02',
components: {
// 左辺にカスタムタグ・右辺に先程定義したコンポーネントの変数を指定
'my-component': myComponent
}
})
上記のコードではカスタムタグになる部分をケバブケースで記述しているが、キャメルケースやパスカルケースも使用可能。template(HTML)内ではケバブケース<my-component>
で記述する。
※ケバブケース → 単語をハイフン( - )で区切る。(kebab-case)
※キャメルケース → 最初の単語以外の頭文字を大文字にする。(camelCase)
※パスカルケース → 単語の頭文字を常に大文字にする。(PascalCase)
コンポーネントのオプション
コンポーネントのオプションにはVueインスタンスと同様にdata、computed、methodsなどのプロパティやメソッドを定義することができる。
→Vueインスタンスのオプションについてはこちら
templateオプション
templateオプションにはコンポーネント内で表示させたい内容を記述する。
🌟 ポイント
templateオプションは1つのルート要素しか定義することができないので、複数のタグを記述したい場合はdivタグなどで囲ってルート要素を1つにする必要がある。
※templateオプション内で改行する場合は、シングルクオート( ' )をバッククオート( ` )にする。
Vue.component('my-component',{
template: `<div>
<p>Hello</p><p>World!</p>
</div>`
})
dataオプション
使い方はVueインスタンスと同じ。
ただし、コンポーネントのdataオプションはオブジェクトを返す関数
にする必要がある。
dataを関数にすることでdataをもつ複数のコンポーネントがある場合、コンポーネントごとにdataの値を保持することができる。
Vue.component('my-component',{
template: '<p>{{ message }}</p>',
data: function() {
return {
message: 'Hello World!'
}
}
})
定義したデータやメソッドにアクセス
コンポーネントにはそれぞれスコープがある。定義されたデータやメソッド、テンプレートがコンポーネントのスコープになる。スコープ内のデータやメソッドにはthis
を使ってアクセスすることができる。
Vue.component('my-component',{
template: '<p>{{ message }}</p>',
data: function() {
return {
count: 0
}
},
methods: {
countUp: function() {
// dataオプションで定義したcuontにアクセス
return this.count++
}
}
})
コンポーネントインスタンス
🌟 コンポーネントは設計図
コンポーネントはUI部品、簡単に言えば機能をもつことのできるHTML要素の設計図である。設計図から作成された実体をインスタンス
と呼ぶ。
↓のように同じコンポーネントを複数回使用した時、見た目や動きは全く同じでも、これらはmy-componentから作られたそれぞれ別のインスタンスとなる。
<div id="app">
<my-component></my-component>
<my-component></my-component>
</div>
データの受け渡し
コンポーネントの親子関係
templateオプションで他のコンポーネントを使用すると親子関係になる。
親 → コンポーネントを使用している
子 → コンポーネントとして使用されている
// 親コンポーネント
Vue.component('parent',{
template: '<child></child>'
})
// 子コンポーネント
Vue.component('child',{
template: '<p>子コンポーネント</p>'
})
new Vue({
el: 'app'
})
下記のようにルートインスタンスでコンポーネントを使用している場合も、ルートインスタンスが親コンポーネントとなり、使用されているコンポーネントが子コンポーネントとなる。
<div id="app">
<parent></parent>
</div>
子コンポーネント内でもさらにコンポーネントを使用することできる。こうしてコンポーネントはネスト(入れ子)していき、DOMのようなツリーで構造化されていく。
親から子へ props
親コンポーネントのデータをプロパティとして子コンポーネントに渡すことができる。
親コンポーネント
まず、親コンポーネントでは、子コンポーネントに渡したいデータを定義する。
下記ではtitle
プロパティにテキストを、val
プロパティに親コンポーネントのdataオプションで定義したmessageを定義している。
データバインディングを利用することでリアクティブ(動的)な親のデータを子に渡すことができる。
このプロパティはデータを入れる箱のようなものなので、名前はなんでもOK。
<div id="app">
<child-component title="タイトル" :val="message"></child-component>
</div>
new Vue({
el: 'app',
data: {
message: 'Hello'
}
})
子コンポーネント
子コンポーネントではprops
オプションで受け取りたいデータのプロパティを定義する。
親で定義したプロパティをpropsとして受け取ることで自分のデータのように扱うことができる。
Vue.component('child-component',{
// テンプレート内で親から受け取ったデータを使用
// 子コンポーネントには「タイトル」と「Hello」が表示される
template: `<div>
<h1>{{ title }}</h1>
<p>{{ val }}</p>
</div>`,
// propsで親からvalプロパティを受け取る
porps: [ 'title','val' ]
})
propsは単方向のため、子から親へデータを渡すことはできない
。
子から親にデータを渡すにはカスタムイベントと$emitを使用する(後述)
propsで受け取ったデータは書き換えてはいけない
propsでリアクティブなデータを受け取った場合、親側でデータを更新すると連携している子側のデータも更新される。
しかし、propsで受け取ったデータは親から借りているだけなので子側で勝手に書き換えることはできない。
子側の都合でデータを書き換えたい場合は算出プロパティを使って新しいデータを作成する。
Vue.component('child-component',{
// テンプレート内にはHello Worldと表示される
template: '<p>{{ message }}</p>',
porps: [ 'val' ],
computed: {
// データvalを加工して、新しいデータmessageを定義
message: function() {
return this.val + 'World';
}
}
})
new Vue({
el: 'app',
data: {
message: 'Hello'
}
})
<div id="app">
<child-component :val="message"></child-component>
</div>
子から親へ $emit
カスタムイベント
子コンポーネントからは、子側では発生させたイベントに応じて親側でもイベントを発生させたり、子のデータを親に渡すことができる。
子から親へイベントを発生
子でのイベントに応じて親でもイベントを発生させるには、カスタムイベント
とインスタンスメソッドの$emit
を使用する。
↓下記のような流れでイベントを発生させる
① 子コンポーネント : イベントが発生したら処理を実行
② 子コンポーネント : $emit
メソッドを実行
③ 親コンポーネント : カスタムイベント
を発生
子コンポーネント
① イベントが発生したら処理を実行
まず、テンプレート内にイベントを設定する。
Vue.component('child-component',{
// ボタンがクリックされたらhandleClickを実行
template: '<button @click="handleClick">イベント実行</button>'
})
② $emit
メソッドを実行
先程設定したイベントの処理(handleClick)の中に$emitメソッドを記述。
$emit
メソッドではカスタムイベント
を指定する。指定したカスタムイベントを親側で発生させるトリガー(きっかけ)の役割を果たすのが$emitである。
カスタムイベント
はイベントを自作できるもので、Vueのイベントハンドラv-on:click
でいうclick
の部分を自由にカスタマイズすることができる。
Vue.component('child-component',{
// ボタンをクリックするとhandleClickが実行
template: '<button @click="handleClick">イベント実行</button>',
methods: {
// handleClickが実行されると、$emitメソッドが実行され、イベントchilds-eventが発生する
handleClick: function() {
// $emitメソッドを実行
this.$emit('childs-event')
}
}
})
親コンポーネント
③ カスタムイベント
を発生
親側では子のカスタムタグにカスタムイベントを記述。イベントが発生した時の処理(メソッド)も記述する。
これで子側でイベントが発生すれば$emitが実行され、親側でもイベントが発生、処理が実行される。
<div id="app">
<!-- 子コンポーネントにカスタムイベントchild-eventsをハンドル -->
<!-- 子側のイベントの発生を受け取ってchilds-eventが発生、parentMethodが実行される -->
<child-component @childs-event="parentMethod"></child-component>
</div>
new Vue({
el: 'app',
methods: {
// parentMethodが実行されたらアラートを表示
parentMethod: function() {
alert('子からイベントを受け取りました!')
}
}
})
子から親へデータを渡す
先程の子から親へイベントを発生させる仕組みを利用して、子のデータを親に渡す。
$emitの引数として子のデータを渡すことで親に送ることができる。
※複数のデータを渡す場合は引数にオブジェクトを渡すこともできる。
子コンポーネント
Vue.component('child-component',{
// inputに「Hello」と入力したとする
// クリックイベントでhandleClickを実行
template: `<div>
<input type="text" v-model="text">
<button @click="handleClick">送信ボタン</button>
</div>`,
data: function() {
return {
text: ''
}
},
methods: {
// handleClickが実行されると、$emitメソッドが実行され、イベントfrom-childが発生する
handleClick: function() {
// $emitメソッドを実行
this.$emit('from-child', this.text)
}
}
})
親コンポーネント
<div id="app">
<!-- Helloと表示される -->
<p>{{ message }}</p>
<!-- 子側のイベントを受け取り、form-eventが発生、receiveMessageが実行される -->
<child-component @form-event="receiveMessage"></child-component>
</div>
new Vue({
el: 'app',
data: {
message: ''
},
methods: {
// 子のデータtextを引数(message)として受け取る
receiveMessage: function(message) {
// 引数を親のデータmessageに代入
this.message = message;
}
}
})
$emitの引数を親コンポーネントの引数として使用
子コンポーネントの$emitで引数として渡しているデータを親コンポーネントのメソッドでも引数として使用するには$event
を使用する。
$event = $emitの引数
<child-component @childs-event="parentMethod($event, parentsData)"></child-component>
new Vue({
el: 'app',
data: {
parentsData: '親のデータ'
},
methods: {
// 引数に子の引数(child)と親のデータ(parent)を渡す
parentMethod: function(child, parent) {
// 処理
}
}
})
コンポーネントとslot(スロット)
slotは親側で子コンポーネントをカスタマイズしたい時に利用するもので、親側から子コンポーネントにテンプレートを差し込むことができる。
子コンポーネント
子側では、親側で挿入されたコンテンツを表示させたい場所に<slot>
タグを記述する。
Vue.component('child-component',{
template: '<p><slot></slot></p>',
})
親コンポーネント
<div id="app">
<child-component>親側からテキストを挿入</child-component>
</div>
slotの初期値
親側から子コンポーネントにslotの挿入がない場合に、デフォルトで表示させる内容をslotタグの中に記述することができる。
Vue.component('child-component',{
template: '<p><slot>親側で挿入がない場合にこちらを表示する</slot></p>',
})
<div id="app">
<!-- 「親側で挿入がない場合にこちらを表示する」が表示される -->
<child-component></child-component>
</div>
slotに名前をつける
複数slotを使用する場合は、識別するためにそれぞれのslotに名前をつけることができる。
子コンポーネント
子側ではslotタグにname属性をつけ、slot名をつける。
Vue.component('child-component',{
template: `<div>
<h1><slot name="title"></slot></h1>
<p><slot name="content"></slot></p>`,
})
親コンポーネント
親側ではslot部分に挿入するタグにslot属性を追加して、先程子側で定義した名前をslot属性に渡してあげる。
<div id="app">
<child-component>
<div slot="title">これはタイトル</div>
<p slot="content">これはコンテンツ</p>
</child-component>
</div>
そうすると親コンポーネントのHTMLの構造は下記のようになる。
<div id="app">
<div>
<h1><div>これはタイトル<div></h1>
<p><p>これはコンテンツ<p></p>
</div>
</div>
これだと余分なタグがあるのでもう少しスッキリさせたい。
template
タグを使用することでスッキリとした構造にすることができる。
<div id="app">
<child-component>
<template slot="title">これはタイトル</template>
<template slot="content">これはコンテンツ</template>
</child-component>
</div>
templateを使用すると下記にようになる↓
<div id="app">
<div>
<h1>これはタイトル</h1>
<p>これはコンテンツ</p>
</div>
</div>