87. 親子間のデータの受け渡しの必要性を理解する
アプリケーションのApp.vue
は親コンポーネント⇄LikeNumber
、LikeHeader
は子コンポーネント
→App.vueではLikeNumber、LikeHeaderを使っている。
(src/App.vue) <template> <div> <LikeHeader></LikeHeader> <h2>{{ number }}</h2> <LikeNumber :total-number="number" @my-click="incrementNumber"></LikeNumber> <LikeNumber :total-number="number"></LikeNumber> </div> </template>
なぜコンポーネント間でデータの受け渡しを行うの?そのコンポーネント自身がdataを持てばいいのでは?という疑問を解消
ex.)全てのブログの内容は日々更新されるモノで、DBから取ってくるもの。
createdでサーバーから取ってきたデータはApp.vue
にある
→コンポーネントはApp.vue
の中で分かれているので、LikeHeaderコンポーネント
に「ブログのタイトルはこれですよ」と渡してあげることになる。
88. propsを使用して親から子にデータを渡そう
子コンポーネントでpropsを定義する。
→配列で書く。使いたいプロパティ名を書く。
(src/components/LikeNumber.vue) <template> <div> <!-- 受け取ったデータを使用している --> <p>いいね({{ number / 2 }})</p> <button @click="increment">+1</button> </div> </template> <script> export default { // データの受け口。プロパティ名を指定 props: ["number"], methods: { increment() { this.number += 1; } } }; </script>
親では属性のように書く
以下は静的なデータの受け渡し。
(src/App.vue) <template> <div> <LikeHeader></LikeHeader> <h2>{{ number }}</h2> <!-- データの渡し口。属性っぽい書き方 --> <LikeNumber number="6"></LikeNumber> <LikeNumber number="6"></LikeNumber> </div> </template>
動的なデータを渡すにはv-bindを使う。
(src/App.vue) <template> <div> <LikeHeader></LikeHeader> <h2>{{ number }}</h2> <!-- データの渡し口にv-bindを使う --> <LikeNumber :number="number"></LikeNumber> <LikeNumber :number="number"></LikeNumber> </div> </template> <script> import LikeHeader from "./components/LikeHeader.vue"; export default { data() { return { number: 10 }; }, components: { LikeHeader: LikeHeader } } </script>
89. propsの名前はJavaScriptではキャメルケース、HTML内ではケバブケースにすることをお勧めします
propsの書き方...キャメルとケバブがある。
→子ではキャメル。親ではケバブを推奨(html文だから)
(src/components/LikeNumber.vue) <template> <div> <p>いいね({<200c>{ totalNumber / 2 }})</p> <button @click="increment">+1</button> </div> </template> <script> export default { // キャメルケース props: ["totalNumber"], methods: { increment() { this.number += 1; } } }; </script>
(src/App.vue) <template> <div> <LikeHeader></LikeHeader> <h2>{<200c>{ number }}</h2> <!-- キャメルでもケバブでもいける。ケバブを推奨 --> <LikeNumber :totalNumber="number"></LikeNumber> <LikeNumber :total-number="number"></LikeNumber> </div> </template>
90. コンポーネント内でpropsを扱う時はdataのように使用する
script内で使う
コンポーネント内でpropsで受け取ったデータは、普通のdataのアクセスと同じようにthisでアクセスできる。
(src/components/LikeNumber.vue) <template> <div> <p>いいね({<200c>{ halfNumber / 2 }})</p> <button @click="increment">+1</button> </div> </template> <script> export default { props: ["totalNumber"], computed: { // ES6の関数の書き方 halfNumber() { //propsで受け取ったデータに、 thisでコンポーネント内からアクセスしている return this.totalNumber } }, methods: { increment() { this.number += 1; } } }; </script>
91. バリデーションを使用して、プロパティに意図しない値が渡されるのを防ぐ
オブジェクトを扱える
propsで指定した型と、親から受け取ったデータの型が違うと、エラーが出る。
(src/components/LikeNumber.vue) <script> export default { props: { // 右辺に型を指定 totalNumber: Number },
[Vue warn]: Invalid prop: type check failed for prop "totalNumber". Expected String with value "10", got Number with value 10.
(src/components/LikeNumber.vue) <script> export default { props: { // 右辺に型を指定 totalNumber: { type: Number, required: true } }, (src/App.vue) <LikeNumber></LikeNumber>
[Vue warn]: Missing required prop: "totalNumber"
defaultは配列、オブジェクトの型などは関数にして初期値を設定する。
(src/components/LikeNumber.vue) <script> export default { props: { // 右辺に型を指定 totalNumber: { type: Number, default: 7 } },
92. 複数のpropsをつける方法
複数のpropsを指定
(src/components/LikeNumber.vue) <script> export default { props: { // totalNUmberを受け取る totalNumber: { type: Number, default: 30 }, // testPropsを受け取る testProps: { type: String } },
(src/App.vue) <template> <div> <LikeHeader></LikeHeader> <h2>{<200c>{ number }}</h2> <LikeNumber></LikeNumber> <!-- 渡し口を2つ書いている --> <LikeNumber :total-number="number" :test_props="test"></LikeNumber> </div> </template> <script> import LikeHeader from "./components/LikeHeader.vue"; export default { data() { return { number: 14 }; }, components: { LikeHeader: LikeHeader }, methods: { incrementNumber(value) { this.number = value } } } </script>
配列でも複数のpropsを指定できる
(src/components/LikeNumber.vue) <script> export default { // 配列で複数のpropsを指定 props: ["totalNumber", "testProps"], computed: { halfNumber() { return this.totalNumber } }, methods: { increment() { this.number += 1; } } }; </script>
93. $emitメソッドを使って子から親にデータを渡す(厳密ではない)
propsのおさらい
子コンポーネントにprops
というdataの受け口があり、親コンポーネントに<LikeNumber :total-number="number"></LikeNumber>
という渡し口があった。
$emit
子から親は$emit
を使う。
→イベントを発生させてdataの受け渡しを行う
インスタンス内だからthisを使って、this.$emit
とする。
$emitの引数
- データの名前(カスタムイベント名)。何でもいい
- データ自身をいれる(コンポーネント内でthisでアクセス)
子コンポーネント側
clickした瞬間に$emitが発火して、dataが親に渡ってnumberに代入する
(src/components/LikeNumber.vue) <template> <div> <p>いいね({{ halfNumber }})</p> <!-- ①クリックした瞬間にincrementメソッドの$emitが発火する --> <button @click="increment">+1</button> </div> </template> <script> export default { // 親からデータを受け取る props: ["totalNumber"], computed: { halfNumber() { return this.totalNumber / 2 ; } }, methods: { increment() { // 子に$emitを書く // 第1引数にデータの名前(カスタムイベント名)、第2引数にデータ自身(ここではtotalNumberに+1している)をいれる // ②$emitが発火することで、親コンポーネントに、my-clickイベントが発火するという情報を渡す this.$emit('my-click', this.totalNumber + 1); } } }; </script>
親コンポーネント側
まずv-on
を書く。v-onの引数に自分で作ったイベント名を入れる。
js式を書けるので、$event
でデータを受け取る。
(numberという)変数にdataを入れる。
(src/App.vue) <template> <div> <LikeHeader></LikeHeader> <h2>{{ number }}</h2> <!-- my-clickイベントが発火 --> <!-- ③$eventでデータを受け取り、numberという変数にdataを入れる --> <LikeNumber :total-number="number" v-on:my-click="number = $event"></LikeNumber> <LikeNumber :total-number="number"></LikeNumber> </div> </template> <script> import LikeHeader from "./components/LikeHeader.vue"; export default { data() { return { number: 14 }; }, components: { LikeHeader: LikeHeader } } </script>
軽くまとめると...
$emitは子コンポーネントの好きなタイミングで、親コンポーネントのメソッドを発火できる。
親のメソッドを発火させる起動スイッチみたいなもので、それに便乗して第二引数でdataを渡していただけ。
$emitはカスタムイベントを作るモノといえる。
<LikeNumber :total-number="number" @abort="number = $event"></LikeNumber>
メソッドで書きたい場合
value部分にemitの第二引数のデータが入る
(src/components/LikeNumber.vue) <LikeNumber :total-number="number" @my-click="incrementNumber"></LikeNumber> <script> import LikeHeader from "./components/LikeHeader.vue"; export default { data() { return { number: 14 }; }, components: { LikeHeader: LikeHeader }, methods: { // value部分にemitの第二引数が入る incrementNumber(value) { this.number = value } } } </script>
子供が親のdataを変えることはできない(親が子のdataに依存することはないから)
94. なぜ$emitはpropsと違ってこんなに複雑なのか
$emitという送り口から this.totalNumber + 1の結果を my-clickという名前で 送る
以下は子コンポーネントの中では値が変わってるけど、親の値は変わっていない
以下のemitを使わない書き方だと、親のdataは変わらない。具体的には子のカウントボタンをクリックして、子コンポーネントのデータは増加しているのに、親コンポーネントのデータは増加しない。
→なぜなら、子から親のdataは変えられないから!
→子から親のデータを変えられるとアプリケーションとして挙動が読みにくくなる。こうやって制限するからデータフローがわかりやすくなる。
→$emit
を使って、dataの値を上手く調理するのは親の仕事!
(src/components/LikeNumber.vue) <template> <div> <p>いいね({{ halfNumber }})</p> <button @click="increment">+1</button> </div> </template> <script> export default { props: ["totalNumber"], computed: { halfNumber() { return this.totalNumber / 2 ; } }, methods: { increment() { // 以下のemitを使わない書き方だと、親のdataは変わらない(子のdataだけ変わる) // 子から親のdataは変えられないから this.totalNumber += 1; // 一旦$emitをコメントアウト // this.$emit('my-click', this.totalNumber + 1); } } }; </script>
(src/App.vue) <template> <div> <LikeHeader></LikeHeader> <h2>{{ number }}</h2> <LikeNumber :total-number="number" @my-click="incrementNumber"></LikeNumber> <LikeNumber :total-number="number"></LikeNumber> </div> </template> <script> import LikeHeader from "./components/LikeHeader.vue"; export default { data() { return { number: 14 }; }, components: { LikeHeader: LikeHeader }, methods: { incrementNumber(value) { this.number = value } } } </script>
95. propsで配列とオブジェクトを渡す時は、参照渡しになるので注意する
propsは親のデータに依存して、子の値がぽんぽん変わる。
→親のデータが変わるだけで、子のデータも変わる
配列とオブジェクトとなっているdataをprops
で受け取った場合、その値を書き換える場合は、親の値も変わる。
→これは参照渡し(同じメモリから参照している)がされており、親も子もおなじオブジェクトを参照しているから。
props: ["totalNumber"]
のtotalNumberの値がオブジェクトの時などのこと。
96. $emitで作るカスタムイベント名はケバブケース(kebab-case)にする
カスタムイベント名はケバブケース推奨
this.$emit('my-click', this.totalNumber + 1);
ブラウザはhtmlを読んでからjsを読みこむ
→htmlは大文字と小文字の区別をしない
→カスタムイベントをjsで使うことは絶対ない。あくまで以下のようなhtmlで使われる
<LikeNumber :total-number="number" @my-click="incrementNumber"></LikeNumber>
→じゃあ、html推奨のケバブケースにしようぜ。