Vue.jsでチェックボックスを双方向にデータバインディングする例としては、多くの場合v-model
ディレクティブが用いられます。けれど、単一ファイルコンポーネントを使って、子コンポーネントのチェックボックスをバインディングしようとすると、v-model
だけで手軽にはできません。この場合のやり方について、ご説明します。
ひな形のVueプロジェクトをつくる
親子の単一ファイルコンポーネントを使ったプロジェクトは、Vue CLIでひな形が簡単にできます。このひな形からサンプルをつくることにしましょう。つぎのように、vue create
コマンドにプロジェクト名(binding_checkbox
)を添えて実行します。詳しくは、「Vue CLI 3入門 04: プロジェクトをつくる」をお読みください
$ vue create binding_checkbox
プロジェクトのディレクトリに移ったうえで、npm run serve
と打ち込めば、アプリケーションがローカルサーバーで開きます。URLはhttp://localhost:8080/です。
親と子のコンポーネントにチェックボックスを加える
ひな形は、アプリケーション(vue:src/App.vue
)に子コンポーネント(src/components/HelloWorld.vue
)がひとつ加えられています。それぞれのコンポーネントファイル(VUE)の中身を以下のように書き替えて、親と子にひとつずすつチェックボックスとラベル、およびボタンの一式を加えましょう(図001)。
図001■親と子のコンポーネントにチェックボックス/ラベル/ボタンが加えられた
<template>
<div id="app">
<input type="checkbox" id="parent">
<label for="parent">parent</label>
<button>toggle</button>
<HelloWorld :check_child="check_child"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
components: {
HelloWorld
},
data() {
return {
check_parent: false,
check_child: false
};
}
}
</script>
<template>
<div class="hello">
<input type="checkbox" id="child">
<label for="child">child</label>
<button>toggle</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
check_child: Boolean
}
}
</script>
データバインディングは、簡単な準備だけ済ませました。まずは、アプリケーション(src/App.vue
)です。<script>
のVueインスタンスにはdata
オプションに、チェックボックスの状態をもたせる予定のプロパティが、親(check_parent
)と子(check_child
)のためにひとつずつ用意してあります。そして、子のプロパティは、<template>
にディレクティブv-bind
(省略記法:
)でバインドしなければなりません。
つぎに、子コンポーネント(src/components/HelloWorld.vue
)では、<script>
のVueインスタンスにprops
オプションで親からバインドされたプロパティ(check_child
)を受け取ります。これで、子コンポーネントは親からデータバインディングされた値が使えるようになったわけです。
親アプリケーションのチェックボックスを双方向データバインディングする
親アプリケーション(src/App.vue
)の中の双方向データバインディングは、ひとつのモジュール内ですのでv-model
ディレクティブが使えます。したがって、ディレクティブをつぎのようにチェックボックス(<input>
)に加えるだけです。また、ボタンはv-on:click
(省略記法@
)イベントにリスナーメソッド(toggleParentCheck()
)を定め、バインドされたプロパティ(check_parent
)のブール値が反転できるようにしました。
<template>
<div id="app">
<input type="checkbox" id="parent" v-model="check_parent">
<button @click="toggleParentCheck">toggle</button>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
methods: {
toggleParentCheck() {
this.check_parent = !this.check_parent;
}
}
}
</script>
これで、双方向のデータバインディングができました。チェックボックスの操作だけでなく、ボタンでもチェックのオン/オフが切り替えられます。チェックボックスの状態がデータ(check_parent
)とバインドされているからです。
子コンポーネントのチェックボックスを双方向データバインディングする
単一ファイルコンポーネントで別モジュールにした場合、データバインディングは親から子へと子から親へとを、それぞれ定めなければなりません(「コンポーネントで v-model を使う」参照)。
前掲ふたつのコンポーネントのコードでは、親(src/App.vue)から子(src/components/HelloWorld.vue)にバインドするデータ(check_child
)は渡されていました。それを<template>
で以下のようにv-bind:value
(省略記法:value
)に与えれば、要素が値を取り出せます。値は確かめられるように、{{}}
でテキストとして示しました。
子から親へは、イベントで送ります。<input>
要素を操作するイベントはv-on:input
(省略記法@input
)です。リスナーのメソッド(toggleChildCheck()
)は、$emit()
メソッドで親にイベント(toggleChildCheck
)と引数の値を送ります。
<template>
<div class="hello">
<input type="checkbox" id="child"
:value="check_child"
@input="toggleChildCheck">
<button @click="toggleChildCheck">toggle</button>
{{check_child}}
</div>
</template>
<script>
export default {
methods: {
toggleChildCheck() {
this.$emit('toggleChildCheck', !this.check_child);
}
}
}
</script>
親のアプリケーション(src/App.vue
)は、v-on
(@
)で受け取ったイベント(toggleChildCheck
)から、リスナーメソッド(toggleChildCheck()
)を呼び出し、引数(value
)の値でバインドしているプロパティ(check_child
)を改めます。これで、子から親へのデータバインディングもでき上がりです。
<template>
<div id="app">
<HelloWorld
@toggleChildCheck="toggleChildCheck"/>
</div>
</template>
<script>
export default {
methods: {
toggleChildCheck(value) {
this.check_child = value;
console.log(this.check_child);
}
}
}
</script>
ただし、ひとつ問題があります。ボタンでプロパティのブール値は変わるものの、チェックボックスのチェック表示が切り替わりません(図002)。
図002■ボタンで変わるのはプロパティ値だけでチェック表示はそのまま
checked属性をバインディングする
チェックボックスの<input>
要素(type
属性checkbox
)でチェックのあるなしはchecked
属性が決めます。したがって、この属性もバインディングしなければならないのです(とくにvalue
属性値を使わない場合は、この属性へのバインディングはなくしても構いません)。
<template>
<div class="hello">
<input type="checkbox" id="child"
:checked="check_child"
>
</div>
</template>
これで、親アプリケーションと同じように、、子コンポーネントのチェックボックスはボタンでも操作できるようになります。
v-modelをカスタマイズして子コンポーネントに使う
v-model
ディレクティブはカスタマイズすれば、別モジュールの子コンポーネントと双方向にデータバインディングできます。デフォルトのv-model
が値を受け取るプロパティはvalue
で、送るイベントはinput
です。Vueインスタンスにmodel
オプションを定めれば、これらが変えられます(「v-model を使ったコンポーネントのカスタマイズ」参照)。
今回は、バインドするプロパティをchecked
にすれば解決です。ただ、チェックボックスの操作はinput
だけでなく、change
イベントでも扱えます(「<input type="checkbox">」参照)。そこで、イベントもchange
に変えて確かめてみましょう。書き替える子コンポーネント(src/components/HelloWorld.vue
)のコードはつぎのとおりです。
<template>
<div class="hello">
<input type="checkbox" id="child"
:checked="check_child"
@change="toggleChildCheck">
<!-- :value="check_child"
@input="toggleChildCheck" -->
</div>
</template>
<script>
export default {
model: {
prop: 'check_child',
event: 'change'
},
}
</script>
親アプリケーション(src/App.vue
)は、<template>
で子コンポーネントに与えていたプロパティのv-bind
(:
)をつぎのようにv-model
に差し替えます。子からのイベント(toggleChildCheck
)の受け取りは、そのまま使うのでなくせません。
<template>
<div id="app">
<!-- HelloWorld :check_child="check_child" -->
<HelloWorld v-model="check_child"
@toggleChildCheck="toggleChildCheck"/>
</div>
</template>
これでも、子コンポーネントのチェックボックスが双方向にデータバインディングできます。もっとも、前項のコード例より、とくに短くもわかりやすくもなっていません。明示的にv-model
ディレクティブを使う必要がある場合でなければ、前項のやり方で差し支えないでしょう。