vue.js component間のデータの受け渡し(備忘録)
環境
端末:MacBook Pro (15, 2017)
OS: Mac OS Mojave(version 10.14.6)
npm: 6.9.0
Vue CLI version: 3.9.0
Vue.js version: 2.6.10
vuetify:2.1.0
ブラウザで見やすい様にvuetifyを使っています。
vue.jsの特徴であるコンポーネント。アプリケーションのレイアウトを分子レベルに分解して、再利用性を高めることが求められます。検索機能一つとってもツールとして、パーツとして考えることができれば何度も同じコードを記述することもなく、再利用できますが、各コンポーネントがどの様にデータを受け取り、渡すことができるのかが必須となります。基礎中の基礎ですが、備忘録として記します。なおvue.2.6がリリースされてv-slot機能が備わりました。コンポーネント間のデータの受け渡しは今後も改善されていく可能性がありますし、気づいたことも追記していきます。
親コンポーネントから子コンポーネントへ
props
型: Array | Object
配列でdataのプロパティを渡す
親コンポーネントの設定
① 子コンポーネントを親コンポーネントにimportする
② components:{}に子コンポーネントを指定する
③ データを送りたい子コンポーネントのタグを用意する(この場合Props.vue)
<template>
<v-app>
<v-container>
<v-layout justify-center mt-10>
<div>
<h2>App.vue</h2>
<p>親コンポーネントのnumberの値({{ number }})</p>
<p>objectsの値</p>
<ul>
<template v-for="value in objects">
<li :key="value">
{{ value }}
</li>
</template>
</ul>
<!-----③これよりProps------>
<props :number="number" :objects="objects"> </props>
</div>
</v-layout>
</v-container>
</v-app>
</template>
<script>
// ①
import Props from '@/views/Props';
export default {
name: 'App',
// ②
components: {
Props
},
data() {
return {
number: 1,
objects: {
id: 1,
number: 10,
name: '山田太郎'
}
};
}
};
</script>
解説
子コンポーネントに渡したいデータは親コンポーネントにあります。number:1を子コンポーネントに渡します。まず親コンポーネントのプロパティデータを渡すにはimportした子コンポーネントタグに渡したいプロパティをv-bindします。バインドする変数はコロンをつけて渡します。ローワーキャメルケースが良いと思います。
<props :number="number"></props>
<!-----変数はなんでも好きなもので--->
<props :hoge="number"></props>
<script>
// 省略
data(){
return {
number: 1,
objects: {
id: 1,
number: 10,
name: '山田太郎'
}
}
}
</script>
子コンポーネントの設定
① script内でprops:[]にプロパティをセットする(Array配列)
② template内で表示させる
<template>
<v-app>
<v-container>
<div>
<h2>Props.vue</h2>
<p>子コンポーネントに渡された値:({{ number }})</p>
<ul>
<li v-for="(value, key) in objects" :key="value">
{{ key }}-{{ value }}
</li>
</ul>
</div>
</v-container>
</v-app>
</template>
<script>
export default {
name: 'Props',
props: ['number', 'objects']
};
</script>
ブラウザで確認
objectでプロパティを受け取る
オブジェクトでpropsの受け取り方法は受け取りたい型を指定する様です。まだまだ使いこめていないので今後追記していきたいと思います。
オブジェクトでプロパティを受け取る
<script>
// 親コンポーネント
data() {
return {
number: 1,
objects: {
number: 10,
id: 1,
name: '山田太郎'
}
};
},
</script>
<script>
// 子コンポーネント
export default {
props: {
number: Number,
type: Number,
objects: Object
}
};
</script>
子コンポーネントから親コンポーネントへデータを渡す
データを渡したい子コンポーネント
子コンポーネントのデータを親コンポーネントへ渡し、親コンポーネントのイベントを発火させることができます
- 子コンポーネントから渡されたmyNumberの値を使って+10するボタンを配置
- $emitメソッドを使用
- $emitは引数をとる
Props.vue
- templateにボタンを追加
- clickイベントを作成
<!---追加----->
<template>
<v-btn @click="incrementByEmit" class="mt-3" small>+10</v-btn>
</template>
- methodsにイベントを定義
- $emitメソッドを使用して親コンポーネントへイベントを渡します
// 省略
<script>
data() {
return {
myNumber: 0
};
},
methods: {
incrementByEmit() {
this.$emit('my-click', (this.myNumber += 10));
}
}
</script>
- 第一引数に、親コンポーネントで発火するメソッド名をカスタムで作成
- 第二引数に、親コンポーネントから渡されたnumberに+10するイベントの内容を指定
App.vue
- v-onで子コンポーネントのイベントを受け取ります。
- 受け取ったmy-clickイベントをemitEventに定義します
子コンポーネントから受け取ったイベントはカスタムイベントとなって親コンポーネントのイベントを発火させ機能します。
<template>
<props
:number="number"
:objects="objects"
@my-click="emitEvent"
>
</props>
</template>
- script内で受け取るmyNumberの初期値を指定します。
- methodsで受け取るデータの扱いを定義します。
<script>
data() {
return {
number: 1,
objects: {
id: 1,
number: 10,
name: '山田太郎'
},
// 受け取ったデータを格納する初期値
myNumber: 0
};
},
// 受け取ったデータを引数に指定し、親コンポーネントのmyNumberに格納します
methods: {
emitEvent(myNumber) {
this.myNumber = myNumber;
}
}
</script>
ブラウザで確認
slotでhtmlや中括弧の中の値を渡す
propsと違ってまとまったhtmlそのものを親コンポーネントから子コンポーネントへ渡すことができます。
親コンポーネントの設定
前提
コンポーネントの親子関係を作成
- 子コンポーネントタグで渡したいhtmlを挟む
<template>
<v-app>
<h3 id="parent-h3">Parent.vue</h3>
<!---子コンポーネント start----->
<child>
<h3 id="child-h3">ここからChild.vue</h3>
</child>
<!---子コンポーネント end----->
</v-app>
</template>
<script>
import Child from './Child'
export default {
name: 'Parent',
components: {Child}
}
</script>
<style scoped>
#parent-h3 {
background-color: lightgray;
}
#child-h3 {
background-color: gray;
}
</style>
子コンポーネントの設定
渡されたデータを受け取るためにslotタグを配置
<template>
<v-app>
<slot></slot>
</v-app>
</template>
<script>
export default {
name: 'Child',
data() {
return {};
}
};
</script>
ブラウザで確認
複数のslotは可能か
子コンポーネント側のslotタグを複数用意することで量産できます。
画像で確認
別のhtmlも追加したい時はどうか
<!--templateのみ--->
<template>
<v-app>
<h3 id="parent-h3">Parent.vue</h3>
<child>
<h3 id="child-h3">ここからChild.vue</h3>
</child>
<child>
<p>もう一つのChildコンポーネント</p>
</child>
</v-app>
</template>
結果は反映されません。
解決策
slotに名前をつけて判別します。
- 親コンポーネント側
- 子コンポーネントのchildタグ内でtemplateタグを用意し、渡したいhtmlを挟む
- templateタグにv-slot:名前を付与する(v-slotディレクティブ)
- vue version 2.6.0以上
<!--templateのみ--->
<template>
<v-app>
<h3 id="parent-h3">Parent.vue</h3>
<child>
<template v-slot:child>
<h3 id="child-h3">ここからChild.vue</h3>
</template>
<template v-slot:other>
<h3 id="other-slot-h3">もう一つのslot</h3>
</template>
</child>
</v-app>
</template>
- 子コンポーネント側
<template>
<v-app>
<slot name="child"></slot>
<slot name="other"></slot>
</v-app>
</template>
<script>
export default {
name: "Child",
data() {
return {};
}
};
</script>
ブラウザで確認
注意点
必ずtemplateタグにする必要があります。divタグではできません。
v-slotディレクティブによって判別機能が備わり、任意の場所でslotを使うことができる様になります。
slotの位置関係を変更することができます。まさにコンポーネントの再利用性が発揮されています。
defalut slot
前提
子コンポーネントのタグに挟まれている要素に対してdefault slotが作成されます。
- 子コンポーネントタグのtemplateタグ内に挟まれていない要素はdefalut slotとして扱われる
- この場合は①、②。
<template>
<v-app>
<h3 id="parent-h3">Parent.vue</h3>
<child>
<p>①templateタグで挟まれていない</p>
<template v-slot:child>
<h3 id="child-h3">ここからChild.vue</h3>
</template>
<p>②templateに挟まれていない</p>
<template v-slot:other>
<h3 id="slot-p">もう一つのslot</h3>
</template>
</child>
</v-app>
</template>
このままではtemplate内に収められていない要素は表示すらされません。表示するには子コンポーネントにslotを配置します。
<template>
<v-app>
<h3>Chile.vue</h3>
<slot name="childTitle"></slot>
<p>子コンポーネント</p>
<!----新たに用意したslot start------>
<slot></slot>
<!----新たに用意したslot end------>
<slot name="other-title"></slot>
</v-app>
</template>
ブラウザで確認
この様に子コンポーネントにhtml要素が渡され表示されます。
- 子コンポーネントタグ内のテンプレートタグ内に配置されていないhtml要素はslotタグの位置にひとまとめにされて表示されます。
理由はvue.jsがdefault名でslotを認識するためです。
<!----子コンポーネント---->
<slot name="default" />
<!---親コンポーネント---->
<template v-slot="default" />
slotPropsを使って子コンポーネントから親コンポーネントへデータを渡す
- v-bindで名前をつけて親コンポーネントへ渡す
注意: slotPropsを使用するときは指定したslotにのみ反映されるもので、連動しません
<template>
<v-app>
<h3>Chile.vue</h3>
<slot name="childTitle" />
<p>子コンポーネント</p>
<slot></slot>
<!----userプロパティに名前をつける---->
<slot name="other" :user="user" />
</v-app>
</template>
<script>
export default {
name: "Child",
data() {
return {
// 親コンポーネントへ渡したいデータ
user: {
firstName: "太郎",
lastName: "山田"
}
};
}
};
</script>
親コンポーネント側
- slotProps(渡ってきたデータを受け取るため)
<template>
<v-app>
<h3 id="parent-h3">Parent.vue</h3>
<child>
<p>①templateタグで挟まれていない</p>
<template v-slot:child>
<h3 id="child-h3">ここからChild.vue</h3>
</template>
<p>②templateに挟まれていない</p>
<!----渡ってきたデータを受け取るためv-slot:otherにslotPropsとする----->
<template v-slot:other="slotProps">
<h3 id="slot-p">もう一つのslot</h3>
<!-----{{}}マスタッシュ構文で表示させる------>
<p>{{slotProps.user.lastName}}{{slotProps.user.firstName}}</p>
</template>
</child>
</v-app>
</template>
名前付きslotのv-slotを未使用かつslotPropsを使用する時の省略記法
- これまでのtemplateタグで挟んだ名前付きv-slotを削除しています。
- slotPlopsを受け取るにはv-slotが必要なのですが、templateタグでなく子コンポーネントタグに指定して、省略します。
<!---親コンポーネント---->
<template>
<v-app>
<h3 id="parent-h3">Parent.vue</h3>
<child v-slot:default="slotProps">
<p>①templateタグで挟まれていない</p>
<p>②templateに挟まれていない</p>
<p>{{ slotProps.user.lastName}}さん</p>
</child>
</v-app>
</template>
- 子コンポーネント側でもこれまでのv-slotは削除しています。
- slotPropsを使用していなければvue.jsが認識してname="defalut"とします。
- この場合はslotPropsを使用するのでslotにnameを付与するのは必然となりますので、default以外でも名前は自由につけられます。
<!---子コンポーネント---->
<template>
<v-app>
<h3>Chile.vue</h3>
<p>子コンポーネント</p>
<slot name="default" :user="user" />
</v-app>
</template>
<script>
export default {
name: "Child",
data() {
return {
user: {
firstName: "太郎",
lastName: "山田"
}
};
}
};
</script>
ブラウザで確認
この様にv-slotとtemplateタグは、二つに一つでの使用からdefault slotかつslotPropsを受け取りたい場合においてtemplateタグを省略でき、子コンポーネントタグに配置することができる様になります。
ここで省略は終わらず、さらに省略できます。
- name属性のdefalutを省略できます。
<!---親コンポーネント---->
<template>
<v-app>
<h3 id="parent-h3">Parent.vue</h3>
<!--name属性defaultの記述を省略---->
<child v-slot="slotProps">
<p>Vue.js</p>
<p>slot default</p>
{{ slotProps.user.lastName}}さん
</child>
</v-app>
</template>
さらに省略は続きます。
- v-slotは#に置換できます。
- default slotの場合は#default="slotProps"とします
<!---親コンポーネント---->
<template>
<v-app>
<h3 id="parent-h3">Parent.vue</h3>
<!--v-slotを#に置換---->
<child #default="slotProps">
<p>Vue.js</p>
<p>slot default</p>
{{ slotProps.user.lastName}}さん
</child>
</v-app>
</template>
<!---子コンポーネント---->
<template>
<v-app>
<h3>Chile.vue</h3>
<p>子コンポーネント</p>
<!--name属性defaultの記述を省略---->
<slot :user="user"></slot>
</v-app>
</template>
ブラウザで確認
まとめ
この様に親コンポーネントと子コンポーネントの間でデータを渡し合うことができることを記述してきました。
ざっとやってまいりましたが、propsを使った親コンポーネントから子コンポーネントへのデータを渡すことだったり、$emitを使った子コンポーネントのデータを使いながら親コンポーネントのイベントを発火させたり、slotによってhtmlやデータ、名前付きv-slotでslotの識別、slotPropsによる子コンポーネントのデータを受け取ったりと様々です。しかしながら、ネストが深くなる様なコンポーネントの設計となると、バインディングされたtemplateが見辛くなるように思えます。そのような大掛かりな設計となるとvuexのstore機能を検討すると良いかもしれません。