Vue.js: 子コンポーネントのチェックボックスを双方向にデータバインディングする

Vue.js: 子コンポーネントのチェックボックスを双方向にデータバインディングする:

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■親と子のコンポーネントにチェックボックス/ラベル/ボタンが加えられた

1812005_001.png

src/App.vue
<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> 
src/components/HelloWorld.vue
<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)のブール値が反転できるようにしました。

src/App.vue
<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)と引数の値を送ります。

src/components/HelloWorld.vue
<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)を改めます。これで、子から親へのデータバインディングもでき上がりです。

src/App.vue
<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■ボタンで変わるのはプロパティ値だけでチェック表示はそのまま



1812005_002.png



checked属性をバインディングする

チェックボックスの<input>要素(type属性checkbox)でチェックのあるなしはchecked属性が決めます。したがって、この属性もバインディングしなければならないのです(とくにvalue属性値を使わない場合は、この属性へのバインディングはなくしても構いません)。

src/components/HelloWorld.vue
<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)のコードはつぎのとおりです。

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)の受け取りは、そのまま使うのでなくせません。

src/App.vue
<template> 
  <div id="app"> 
 
    <!-- HelloWorld :check_child="check_child" --> 
    <HelloWorld v-model="check_child" 
        @toggleChildCheck="toggleChildCheck"/> 
  </div> 
</template> 
これでも、子コンポーネントのチェックボックスが双方向にデータバインディングできます。もっとも、前項のコード例より、とくに短くもわかりやすくもなっていません。明示的にv-modelディレクティブを使う必要がある場合でなければ、前項のやり方で差し支えないでしょう。

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)