##はじめに
Vue.jsを勉強していて、少しつまずいたのがコンポーネント。
コンポーネント自体の理解は出来ましたが、propsという言葉が出てきたときによくわからなくなりましたので、自分なりに調べたことを纏めておこうと思います。
##コンポーネントのおさらい
まず、コンポーネントというのは再利用可能な部品みたいなものです。これはVueに限らずどんな言語でも同じような設計思想はあるかと思います。複数の場所にそのまま、あるいは多少カスタマイズして埋め込んで使えるようなものですね。
Vueの実装例は調べればいくらでも出てくると思いますが一応。
Vue.component('hello-component', {
template: '<h1>Hello Vue.js</h1>'
});
new Vue({
el: '#app'
})
<div id="app">
<hello-component></hello-component>
</div>
※HTMLはVueの利用箇所のみ記載しています。
Vueでコンポーネント(ここでは"Hello Vue.js"を表示するテンプレート)を定義しつつ、HTMLでは定義したコンポーネント名(hello-component)を指定する形で利用します。
##コンポーネントの親子関係とは
Vueにおけるコンポーネントの親子関係というのは、以下の解釈であるとのこと。
- 親=コンポーネントを利用する側
- 子=コンポーネントを利用される側
上記の例でいうと、HTMLが親、コンポーネント定義が子という関係になるようです。
もう少し厳密に言うとHTMLが親というよりVueインスタンスが親というのが正しいっぽいです。
(HTMLへの表示=Vueインスタンスのオブジェクト表示なので)
ただし、親子関係というのはこういうパターンだけでなく、コンポーネント同士においても存在します。
簡単にいうと、コンポーネントの中でさらにコンポーネントを利用している場合です。
Vue.component('parent', {
template: '<child></child>'
});
Vue.component('child', {
template: '<p>子コンポーネント</p>'
})
new Vue({
el: '#app'
})
<div id="app">
<parent></parent>
</div>
こんな感じです。
親コンポーネントをparent、子コンポーネントをchildという名前で定義しています。
実用性ない例ですが、関係性だけを示すとこういうことになります。
##propsとは?
これまで示した例だと単に静的な情報を表示するだけで実用的ではありません。
再利用性を考えるとデータのやり取り(つまり必要な処理をするために外部からデータを受け取り、その結果を返す)が必要になりますが、各コンポーネントはそれぞれ独立したスコープを持つので、何らかの形でデータのやり取りをする仕組みが必要です。それを実現するのがpropsというものになります。
使い方としては、親側で引き渡したいデータを指定し、子側のpropsというオプションでその属性を定義します。
注意点は、親側と子側でpropsの指定名が同じ形ではないということ。
親側ではケバブケース、子側ではキャメルケースで記述しないといけないようです。
これが個人的には「急に例外的!」と思って若干受け止めきれていません(汗)
もうおじさんなのでどっちがどっちだったか忘れます(汗汗)
以下に例を示します。
Vue.component('hello-component', {
props:{
showMessage: {
type: String
}
},
template: '<p>{{ showMessage }}</p>'
})
new Vue({
el: '#app',
data: {
message: 'Hello, Vue.js'
}
})
<div id="app">
<hello-component v-bind:show-message="message"></hello-component>
</div>
やろうとしていることは、Vueインスタンスのdataオプションに指定したmessageというプロパティをコンポーネントに渡して、テンプレートを介してHTMLに表示するというものです。
結果だけならコンポーネントを使う必要がないのですが、propsという観点に着目してもらえると助かります。
実装の形はこれまでと同様(Vueインスタンス、コンポーネント)で変わっていません。
HTMLでv-bindを使っているのは単にmessageというプロパティにアクセスしたいからです。
ただし、ここでv-bind属性としてprops(show-message)を指定するということがポイントです。
かつ、上述したように指定名はケバブケース。ここを定義名と同じようにshowMessageとすると動きません。
もう一歩踏み込んだ例を示します。
Vue.component('person-component', {
props:{
showPerson: {
type: Object
}
},
template: '<li>{{ showPerson.name }}({{ showPerson.age }}歳)</li>'
})
new Vue({
el: '#app',
data: {
persons : [
{
name: '山田太郎',
age: 30
},
{
name: '田中一郎',
age: 40
}
]
}
})
<div id="app">
<ul>
<person-component v-for="person in persons" :key="person.name" :show-person="person"></person-component>
</ul>
</div>
この例では氏名と年齢を持ったオブジェクトの配列データを画面に表示しています。
(しつこいようですが、propsの役割を理解するための実装です。)
これもやはり形は変わっておらず、Vueインスタンス、コンポーネントを用意するだけです。
が、HTMLがちょっと難しいかもしれません。私は最初、「ん?」と思いました。
<person-component v-for="person in persons" :key="person.name" :show-person="person"></person-component>
v-forと:key(v-bindを省略)は基本文法なので置いておいて、show-person="person"というところが焦点になります。
1つ前の例と同様propsを指定しますが、Vueインスタンスのdataのプロパティ(persons)を指定していません。代わりに、v-forで取り出す要素(person)を指定します。つまり、persons(配列の全要素)をそのまま渡すのではなく、1つ1つの要素を渡すイメージかと思います。そうすることで、propsには、まず{name: '山田太郎', age: 30}、次に{name: '田中一郎',age: 40}が渡ることになります。
persons(dataのプロパティ自体)を渡せないということではなく、この例のようにリスト表示するために配列の1つ1つを取り出す場合にはこうするという実装になります。
ちなみに、コンポーネントの中のprops定義ではtypeをObjectとしています。受け取るデータがObjectだからですね。
※propsに定義できるオプションは他にもdefault、required、validatorがありますが、説明は割愛します。
##おわりに
終始実用性のない例でしたが、もっとプロダクトレベルの大規模なアプリケーションになるとたくさんのコンポーネントを作ることになり、そのそれぞれで必要なデータのやり取りを行おうとすると、おそらくpropsだけでは状態管理が難しくなると思います。そういうときにはstoreというオブジェクトを使ったり、Vuexというライブラリを使うということになるようです。そこらへんについてもまた追々書いていけたらと思います。