LOGv:20171214

コンポーネントでmodelを使う

確認環境
Vue.js2.5.16

Vue.jsのmodelって入力要素と親要素の双方向データバインディングだよねーという軽い認識でチェックボックスがあるコンポーネントにv-model使ったら盛大にハマったのでメモ

ハマったコード

Vue.component('v-checkbox', {
    props : [
        'value',
        'checked'
    ],
    template : `
        <label class="v-checked">
            <input type="checkbox" :value="value" :checked="checked" />
            v-checkbox
        </label>
    `
});

let app = new Vue({
    el : '#app',
    data(){
        return {
            checked : true
        }
    }
});

v-checkboxというコンポーネントを用意して親からvalueプロパティとcheckedプロパティを受け取る。
受け取った2つのプロパティをテンプレート内のinput要素の各属性にバインドする。

<div id="app">
    <v-checkbox value="hoge" :checked="checked" v-model="checked"></v-checkbox>
    <div>checkbox is checked : {{checked}}</div>
</div>

HTML側でチェックボックスが押されているか押されていないかを把握しておきたいので値をバインディングする。

checkbox is checked : {{checked}}

はバインディングされてチェックボックスを押すたびに

checkbox is checked : true

checkbox is checked : false

になるはずだ。結果・・・

test1.html

動かない。
しかもvalueは親要素に対して指定されているし、inputのvalueはtrueとかわけわからないことになってる。


v-modelのおさらい

コンポーネントで v-model を使うに書いているけど

<input v-model="searchText">

このコードは

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

と同じ意味らしい。
つまり今回のコードに置き換えると

<v-checkbox value="hoge" :checked="checked" v-model="checked"></v-checkbox>

と書けば

<div class="v-checked">
    <input type="checkbox" :value="value" :checked="checked" :value="cheched" @input="checked = $event.target.value" />
    v-checkbox
</div>

という具合にvalueとinputイベントを使って双方向データバインディングを実現しようとする。
checkboxの場合checked属性を確認したいので、そりゃ動かないわけだ。
期待する形は

<div class="v-checked">
    <input type="checkbox" :value="value" :checked="checked" @change="checked = $event.target.checked" />
    v-checkbox
</div>

なので、この形になるようにしてやれば良い。


動くコード

Vue.component('v-checkbox', {
    model : {
        props : 'checked',
        event : 'change'
    },
    props : [
        'value',
        'checked'
    ],
    template : `
        <label class="v-checked">
            <input type="checkbox" :value="value" :checked="checked" @change="$emit('change', $event.target.checked)" />
            v-checkbox
        </label>
    `
});

let app = new Vue({
    el : '#app',
    data(){
        return {
            checked : true
        }
    }
});

modelプロパティはv-modelのプロパティとイベントを指定する。
これでvalueではなくcheckedを、inputではなくchangeを利用するように指定する。

更にデータバインディングするためにデータを投げる必要がある。
この時、投げる値はvalue値ではなくchecked値。

test2.html

これで動いた。


追記:ラジオボタンの場合

ラジオボタンでv-modelを使うとvalueの値が返る。
以下のように実装する

<v-radio name="test2[]" value="上" v-model="test2_val">上のラジオボタン</v-radio>
<v-radio name="test2[]" value="下" v-model="test2_val">下のラジオボタン</v-radio>
<p v-if="test2_val">{{test2_val}}のラジオボタンをチェックしました</p>
<p v-else>見選択</p>
Vue.component('v-radio', {
    model : {
        prop  : 'model',
        event : 'change'
    },
    props : [
        'value',
        'name'
    ],
    template : `
        <label class="v-radio">
            <input type="radio" :value="value" :name="name" @change="$emit('change', $event.target.value)" />
            <slot></slot>
        </label>
    `
});

let app = new Vue({
    el : '#app',
    data(){
        return {
            test1_val : '',
            test2_val : ''
        }
    }
});

v-modelでプロパティにvalue属性を使われるとまずいので定義していないmodelプロパティを指定して
changeイベントが発生した時に親コンポーネントに対してchangeイベントでラジオボタンのvalue値を投げれば良い。

test3.html


まとめ

ドキュメントはしっかり読みましょう。

open close