Vue.jsでコンポーネントの親子間でデータのやり取りを行う方法

最近Vue.jsを使ったプロジェクトに手を出しています。
そこでコンポーネント間で値をやり取りする方法を調べていましたが、
こうするべきでしょ、というのを私なりに探っていった結果をまとめました。

こちらのコードの例で使用しているVueのバージョンは2.5.21です。

# 親から子に単方向でやり取りする方法

親コンポーネントから子コンポーネントに一方的にデータの値を渡して、
子からはそれを変更しても、親に変更は伝搬させない場合のやり方です。

## シンプルに`props`に渡す

`props`で親のもつ値を渡します。Vueを使うなら普通のやり方ですね。

<p data-height="265" data-theme-id="0" data-slug-hash="zypZbe" data-default-tab="js,result" data-user="rhistoba" data-pen-title="vue component test parent -> child" class="codepen">See the Pen <a href="https://codepen.io/rhistoba/pen/zypZbe/">vue component test parent -&rt; child</a> by stoba (<a href="https://codepen.io/rhistoba">@rhistoba</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# 子から親に単方向でやり取りする方法

子コンポーネントのもつデータの変更を、親コンポーネントに一方的に伝搬させる方法です。

## `$emit`を使う

子コンポーネントで`this.$emit`を実行すると、値の変更があったことをイベントとして値とともに親に知らせることができます。
親はそのイベントがトリガされた場合の処理を`methods`に書きます。

<p data-height="265" data-theme-id="0" data-slug-hash="vvpxwL" data-default-tab="js,result" data-user="rhistoba" data-pen-title="vue component test parent <- child" class="codepen">See the Pen <a href="https://codepen.io/rhistoba/pen/vvpxwL/">vue component test parent &lt;- child</a> by stoba (<a href="https://codepen.io/rhistoba">@rhistoba</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

## （好ましくない例）`$parent`で親のプロパティを参照する

子コンポーネントで`this.$parent`を使って親コンポーネントのデータを参照して、そこに直接値を代入する方法です。
しかし、これは親がデータとして何を持つべきかを子が縛ることになるので、使わない方がいいでしょう。

<p data-height="265" data-theme-id="0" data-slug-hash="Qzavwm" data-default-tab="js,result" data-user="rhistoba" data-pen-title="vue component test parent <- child bad case" class="codepen">See the Pen <a href="https://codepen.io/rhistoba/pen/Qzavwm/">vue component test parent &lt;- child bad case</a> by stoba (<a href="https://codepen.io/rhistoba">@rhistoba</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# 親子で双方向にやり取りする方法

親子両方からの変更に対して、双方ともにデータの変更を伝搬させる方法です。

## （不可能な例）子の`props`の値を直接変更する

子の`props`にある、親から渡されたデータを直接変更すればよさそうに思えますが、不可能です。
`props`の子からの変更は親に対して変更を与えません。
参考: https://jp.vuejs.org/v2/guide/components-props.html#%E5%8D%98%E6%96%B9%E5%90%91%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%83%95%E3%83%AD%E3%83%BC

<p data-height="265" data-theme-id="0" data-slug-hash="JwMJYW" data-default-tab="js,result" data-user="rhistoba" data-pen-title="vue component test parent <-> child bad case 1" class="codepen">See the Pen <a href="https://codepen.io/rhistoba/pen/JwMJYW/">vue component test parent &lt;-&rt; child bad case 1</a> by stoba (<a href="https://codepen.io/rhistoba">@rhistoba</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

## （好ましくない例）`$parent`を使う

前述の`$parent`を使って親のデータを直接参照する方法です。
実現こそ可能でコード量こそ少なくて済みますが、前述通り親のデータを子が縛ることになるので、
使わない方がいいでしょう。

<p data-height="265" data-theme-id="0" data-slug-hash="xmpXVK" data-default-tab="js,result" data-user="rhistoba" data-pen-title="vue component test parent <-> child bad case $parent" class="codepen">See the Pen <a href="https://codepen.io/rhistoba/pen/xmpXVK/">vue component test parent &lt;-&rt; child bad case $parent</a> by stoba (<a href="https://codepen.io/rhistoba">@rhistoba</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

## `props`で親のデータを受け取り、`$emit`で変更をトリガする

前述の単方向のやり方を組み合わせた方法です。
冗長な感じに思えますが、親子間の双方向のやり取りはこれが基本となると思います。

<p data-height="265" data-theme-id="0" data-slug-hash="ropLxe" data-default-tab="js,result" data-user="rhistoba" data-pen-title="vue component test parent <-> child" class="codepen">See the Pen <a href="https://codepen.io/rhistoba/pen/ropLxe/">vue component test parent &lt;-&rt; child</a> by stoba (<a href="https://codepen.io/rhistoba">@rhistoba</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

## `v-model`を使う

`v-model`ディレクティブを使うことで、上の冗長な感じをある程度改善することができます。
親側で子コンポーネントを使用する際、`v-model="message"`と記述することで、
`:value="message" @input="message = $event"`と同義になります。
そのため、子コンポーネントでプロパティに`value`を指定して、`$emit('input')`でイベントを送出することで、
親側で子のプロパティ変更のイベントに対する処理を記述する必要が無くなり、コンポーネントが再利用しやすくなります。
参考: https://jp.vuejs.org/v2/guide/components.html#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%A7-v-model-%E3%82%92%E4%BD%BF%E3%81%86

ただし、子コンポーネントのプロパティで使う名前が強制的に`value`になってしまうため、
これにエイリアスを与えてどのような値であるかを明示的にしたいですね。
そのため、`computed`で単純に`value`を返すgetterメソッドを用意するようにしましょう。

双方向のやり取りは、このやり方が最も実用的ではないでしょうか。

<p data-height="265" data-theme-id="0" data-slug-hash="ropwax" data-default-tab="js,result" data-user="rhistoba" data-pen-title="vue component test parent <-> child v-model bad case" class="codepen">See the Pen <a href="https://codepen.io/rhistoba/pen/ropwax/">vue component test parent &lt;-&rt; child v-model bad case</a> by stoba (<a href="https://codepen.io/rhistoba">@rhistoba</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# まとめ

以上、Vueで親子コンポーネントでデータの値をやり取りする方法でした。
重要なのは、変更の処理（`$emit`）と参照（`computed`など）を切り分けて明確に行うことだと考えます。
これは`Redux`や`Vuex`といったfluxアーキテクチャでも同様な考えが含まれますが、
Vue単体でもそれを考慮したプログラミングが必要だと思います。