九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、もちろんクライアントさんのご希望もありますが、最近私のウェブ開発としてはサーバーサイドにLaravel、そしてクライアントサイドにはVue.js(jQueryも併用する場合あり)を使う形で固まってきた感があります。
しかしながら、最近このブログでLaravelの記事を多く公開していますが、あまりVue.jsの記事も公開できていないことに気がついたため、今回は気持ちを初心に戻してVue.jsの基本中の基本、バインディングの実例をまとめることにしました。
記事の最後に今回テストで作成したファイルがダウンロードできるようになっていますので、ぜひVueの学習に役立ててくださいね!
※ 開発環境は、Vue 2.7.17
で、ブラウザはchrome 69
。JavaScriptコードにはES6を使っています。
目次
Vueのフォーム・バインディングとは??
まずは、基本の基本。バインディングについての説明です。
(もう知っている人は実例の項目まで読み飛ばしてください。)
バインディングとは、簡単に言うと、
- 入力ボックスが変更されたら、Vue内の変数が自動的に更新され、
- 逆に、Vue内の変数に変更がされたら、入力ボックスの内容が自動的に更新される
という具合に、二方向からの更新に行ってくれる便利機能です。
例えば、以下のような入力ボックスとボタンの例を見てみましょう。
もし、この入力ボックスがtext
という名前の変数とバインディング(ひも付け)されているとすると、
と、このように入力ボックスの中身が変更になると同時に、Vueが管理しているtext
の中身がカレーが大好物
に更新されます。
また、もしも右にあるボタンをクリックするとtext
の中身がハヤシライスもありだね
に変更コードが実行されるとします。
すると、入力ボックスにはtext
とのバインディングがあるので、実際には入力をしてなくても、以下のように自動的に入力内容が更新されます。
これがバインディングです。
その昔は、いちいちイベントごとにデータの変更をしていましたが、VueやReact、angularなどはバインディングを使ってあらかじめルールを決めておくという発想になり、よりコード記述が少なくて済むようになりました。
では、このバインディングを次の入力形式で見ていくことにしましょう。
- テキストボックス(<input type=”text”>)
- テキストエリア(<textarea></textarea>)
- セレクトボックス(<select></select>)
- チェックボックス(<input type=”checkbox”>)
- ラジオボタン(<input type=”radio”>)
Vueでバインディングを使うには??
Vueでフォーム・バインディングを使うには、v-model
という命令文を使います。例えばテキストボックスの場合は以下のようになります。
<input type="text" v-model="myText">
そして、この場合myText
という名前のデータとバインディングするので以下のようにdata内にmyText
を設定しておく必要があります。
new Vue({ el: '#app', data: { myText: '初期値' // 空白でもOK。 } });
これだけでバインディングの準備が完了します。
※ なお、このv-model
は、<input>
であろうが<select>
であろうが基本的に使い方は同じです。
フォーム・バインディングの全実例
テキストボックス
まずは基本となるテキストボックスのバインディングです。
<html> <body> <div id="app"> <input type="text" v-model="textBox"> <button type="button" @click="changeTextBox()">ボタン</button> <br> <br> 変数の中身: {{ textBox }} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { textBox: '初期値' }, methods: { changeTextBox() { this.textBox = '変更された値'; } } }); </script> </body> </html>
コード内でやっていることは、テキストボックスにdata
で設定されたtextBox
をバインディングし、ボタンがクリックされたらデータが書き換わるというものです。
これを実行するとこうなります。
そして、ボタンをクリックすると、以下のように自動的に内容が書き換わります。
ここで注意してほしいのが、「変数の中身」の部分です。
冒頭でも説明したとおり、バインディングをしていると以下のように手動で入力ボックスを変更しても表示が変更になります。
テキストエリア
テキストエリアは、テキストボックスのバインディングとほぼ同じですが、改行されたデータを扱う部分が違っています。
<html> <body> <div id="app"> <textarea v-model="textArea" rows="3"></textarea> <button type="button" @click="changeTextArea()">ボタン</button> <br> <br> 変数の中身: <pre>{{ textArea }}</pre> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { textArea: '初期値', }, methods: { changeTextArea() { this.textArea = '変更された値\n変更された値\n変更された値'; } } }); </script> </body> </html>
まず<textarea>〜</textarea>
タグにtextArea
という名前のデータをバインディングし、ボタンのクリックイベントでchangeTextArea()
を実行します。
ただし、changeTextArea()
内で変更するデータは\n
、つまり改行コードが入っていることに注意してください。また、データ表示も<pre>〜</pre>
タグを使って見た目が改行がされるようにしています。
実際に表示をしてみるとこうなります。
そして、ボタンをクリックすると以下のように変更になります。
テキストエリアの中身、さらに<pre></pre>
タグ内で表示した中身は、改行された3行のテキストになっています。
もちろん、テキストエリアの中身を変更すると自動的にデータの中身も更新されます。
セレクトボックス
セレクトボックスのバインディングは、テキストボックスより少し複雑なコードになります。
<html> <body> <div id="app"> <select v-model="selectBoxValue"> <option v-for="(label,id) in selectBoxOptions" :value="id">{{ label }}</option> </select> <button type="button" @click="changeSelectBox()">ボタン</button> <br> <br> 変数の中身: {{ selectBox }}({{ selectBoxValue }}) </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { selectBoxValue: 1, selectBoxOptions: { 1: '赤', 2: '青', 3: '黄色', } }, methods: { changeSelectBox() { this.selectBoxValue = 3; // 黄色へ変更 } }, computed: { selectBox() { return this.selectBoxOptions[this.selectBoxValue]; } } }); </script> </body> </html>
まず、今回作成するセレクトボックスの選択肢は以下の3つです。
- 赤
- 青
- 黄色
初期値は「赤」、つまり値は1
なので、dataは以下のようになります。
data: { selectBoxValue: 1, // 初期値(赤) selectBoxOptions: { 1: '赤', 2: '青', 3: '黄色', } },
そして、<select>〜</select>
にv-model
でselectBoxValue
をバインディングします。(つまり、変動する値は「赤」「青」「黄色」ではなく、「1」「2」「3」のどれかとなりり、そのためchangeSelectBox()
内での変更も「3」となっています)
さらに、セレクトボックスの選択肢はv-for
を使ってループさせています。
<option v-for="(label,id) in selectBoxOptions" :value="id">{{ label }}</option>
実行後は以下のようなHTMLになります。
<option value="1">赤</option> <option value="2">青</option> <option value="3">黄色</option>
なお、今回の例でバインディングしているのはselectBoxValue
なので中身を表示しても「1」「2」「3」など数値が表示されるだけです。
そのため、selectBox()
という擬似的な変数をcomputed
内に作成して「赤」「青」「黄色」のいずれかを取得できるようにしています。
computed: { selectBox() { return this.selectBoxOptions[this.selectBoxValue]; // 赤、青、黄色どれかを取得 } }
こうすることで、以下のように記述すればここに色の情報を表示することができるわけです。
{{ selectBox }}
では実際の表示がどうなるか見てみましょう。
変数の中身として「赤」、そして初期値の「1」が表示されています。
ではボタンをクリックして変数の中身を「3」へ切り替えてみましょう。
セレクトボックスも、変数の中身も変更になりました。
もちろん、セレクトボックスを手動で変更しても自動的に変数の内容が更新されます。
チェックボックス(単体)
単体のチェックボックスのバインディングはテキストボックスのようにシンプルに実装することができます。
<html> <body> <div id="app"> <input type="checkbox" v-model="checkBoxValue"> <button type="button" @click="changeCheckBox()">ボタン</button> <br> <br> 変数の中身: {{ checkBoxValue }} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { checkBoxValue: false }, methods: { changeCheckBox() { this.checkBoxValue = !this.checkBoxValue; } } }); </script> </body> </html>
まず、v-model
でデータ変数のcheckBoxValue
をバインディングし、ボタンをクリックしたらchangeCheckBox()
を実行するようにしています。
※ ただし、changeCheckBox()
内ではボタンをクリックするごとにtrue
/false
が切り替わるよう実装しています。
あとは、変数の中身もそのままでOKですので、 {{ checkBoxValue }}
で表示しています。
では実際の表示です。
初期値はfalse
なのでチェックは入っていません。
では、ボタンをクリックして値をtrue
にしてみましょう。
うまく切り替わりました。
もちろん、チェックボックス自体をクリックして変更しても変数の中身が更新されます。
※ ちなみにVueでチェックボックスの値を変更したい場合は、以下のようにtrue-value
とfalse-value
を追加すればOKです。(この例の場合は、チェックされたら値がgood
に、チェックが外れたらbad
になります)詳しくは、たった5秒!Vueのチェックボックスにvalueを設定する方法を参照してください。
<input type="checkbox" v-model="value" true-value="good" false-value="bad">
チェックボックス(複数)
複数回答ができるチェックボックスをバインディングする場合は以下のようにします。
<html> <body> <div id="app"> <label v-for="(label,id) in checkBoxOptions"> <input type="checkbox" v-model="checkBoxValues" :value="id">{{ label }} </label> <button type="button" @click="changeCheckBoxes()">ボタン</button> <br> <br> 変数の中身: {{ checkBoxes }} ({{ checkBoxValues }}) </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { checkBoxValues: [], checkBoxOptions: { 1: '国語', 2: '算数', 3: '理科', 4: '社会', 5: '英語' } }, methods: { changeCheckBoxes() { this.checkBoxValues = ['1', '2', '5']; } }, computed: { checkBoxes() { let values = []; for(let id of this.checkBoxValues) { values.push(this.checkBoxOptions[id]); } return values; } } }); </script> </body> </html>
まず、複数選択できるのは以下の5つです。
- 国語
- 算数
- 理科
- 社会
- 英語
そして、この選択肢をv-for
を使ってチェックボックスを作成し、v-model
でcheckBoxValues
(初期値は空の配列)をバインディングしています。
<label><input type="checkbox" value="1">国語</label> <label><input type="checkbox" value="2">算数</label> <label><input type="checkbox" value="3">理科</label> <label><input type="checkbox" value="4">社会</label> <label><input type="checkbox" value="5">英語</label>
さらに、ボタンのクリックするとcheckBoxValues
を「1」「2」「5」(つまり、「国語」「算数」「英語」)に変更するメソッドchangeCheckBoxes()
を実行していて、さらにデータ表示は選択された教科の名前を取得するcheckBoxes()
を使っています。
では実際に見てみましょう。
初期値は何も指定していないので、チェックボックスも変数の中身も空になっています。
では、ボタンをクリックして変数の中身を変更してみましょう。
チェックされた内容が表示されました。
もちろん、チェックボックスを手動で変更しても自動で変数の中身が変わります。
ラジオボタン
ラジオボタンのバインディングもセレクトボックスと似ていて少しだけ複雑になっています。
<html> <body> <div id="app"> <label v-for="(label,id) in radioButtonOptions"> <input type="radio" name="music" :value="id" v-model="radioButtonValue">{{ label }} </label> <button type="button" @click="changeRadioButton()">ボタン</button> <br> <br> 変数の中身: {{ radioButton }}({{ radioButtonValue }}) </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { radioButtonValue: 1, // 初期値(ロック) radioButtonOptions: { 1: 'ロック', 2: 'ポップ', 3: 'クラシック' } }, methods: { changeRadioButton() { this.radioButtonValue = 3; // クラシックへ変更 } }, computed: { radioButton() { return this.radioButtonOptions[this.radioButtonValue]; } } }); </script> </body> </html>
まず、ラジオボタンの選択肢は以下の3つにしています。
- ロック
- ポップ
- クラシック
そして、v-for
ループを使って<input type="radio">
を3つ用意し、v-model
で「1」「2」「3」の数値を管理するradioButtonValue
をバインディングしています。そのため、changeRadioButton()
の中でも変更するのは数値です。
※ つまりHTMLはこのようになります。
<label><input type="radio" name="music" value="1">ロック</label> <label><input type="radio" name="music" value="2">ポップ</label> <label><input type="radio" name="music" value="3">クラシック</label>
ちなみにここで大事なのがname="*****"
の部分です。HTMLタグと同じくこの名前をそろえておかないと、別でつくったラジオボタンと同じグループになってしまうため、music
などグループ名を指定をおきましょう。
では、最後に変数の中身を表示する部分ですが、ここもセレクトボックスと同じく数値だけでなく、「ロック」「ポップ」「クラシック」も表示させるため、computed
内に疑似変数を設定しています。
では、実際に実行してみましょう。
初期値は「1」なので、「ロック」が表示されています。
ではボタンをクリックしてみましょう。
「3」のクラシックに切り替わりました。
もちろん、ラジオボタンを直接変更してもデータ変数は自動的に更新されます。
Vueコンポーネントのバインディング
例えば、以下のような時間を入力するVueコンポーネントv-time
で、時間と分がどちらも入力されたらバインディングを実行するにはどうすればいいかを見ていきます。
コンポーネントを呼び出している側はこうなります。
<html> <body> <div id="app"> <h3>コンポーネントのバインディング</h3> <v-time v-model="time"></v-time> <br><br> 時間: {{ time }} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> <script> // ←ここにコンポーネントのコード new Vue({ el: '#app', data: { time: '' } }); </script> </body> </html>
つまり、時
と分
2つの入力がそろったらその時間を表示する仕組みです。では、コンポーネントのコードを見てみましょう。
Vue.component('v-time', { data() { return { hours: '', minutes: '' } }, template: '<div>'+ '<input type="text" size="2" v-model="hours" placeholder="時" @input="onInput()"> : '+ '<input type="text" size="2" v-model="minutes" placeholder="分" @input="onInput()">'+ '</div>', methods: { onInput() { if(this.hours && this.minutes) { const time = this.hours +':'+ this.minutes; this.$emit('input', time); } } } });
やっていることは、時
と分
各入力ボックスに入力されたらonInput()が実行され、もし2つとも入力されていたら$emit
でコンポーネントを呼び出した側に入力データを送信しています。
input
イベントが実行されたらバインディングも実行されるので、以下のようにデータが更新されることになります。
このテクニックを使えば、自分の好きなタイミングでイベントを起動することができるので、とてもフレキシブルなコンポーネントを作ることができるでしょう。
v-modelのちょっと便利な機能
lazyでバインディングのタイミングを遅らせる
通常、Vueのバインディングはinput
イベントで実行されるため、入力ボックスに文字が入力された時点でデータ変更されることになります。(日本語入力の場合は変換が終わった瞬間)
これに対して、v-model.lazy
はchange
イベント、つまり入力が終わって入力ボックスからフォーカスが外れた時点でデータ変更されることになります。
では以下のコードを使って違いを見てみましょう。
<html> <body> <div id="app"> <h3>通常バインディング</h3> <input type="text" v-model="normalText"> <br> <br> {{ normalText }} <hr> <h3>Lazy</h3> <input type="text" v-model.lazy="lazyText"> <br> <br> {{ lazyText }} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> <script> new Vue({ el: '#app', data: { normalText: '', lazyText: '' } }); </script> </body> </html>
以下のように通常バインディングの場合は、これまで見てきたとおり入力がされたらすぐにデータが反映されています。
しかし、lazy
を使った場合は入力しただけではデータ変更されることはありません。
その後、入力ボックスからフォーカスが外れた瞬間に以下のようにデータが反映されることになります。
numberで数値としてデータ取得
通常<input>
タグの入力で取得できるデータはString
、つまり「文字列」型になります。ただ、時にはデータを数値として取得したい場合もあるでしょう。そんな場合はv-model.number
が便利です。
では、以下のように通常バインディングと数値バインディングを実装したコードで比較してみましょう。
<html> <body> <div id="app"> <h3>通常バインディング</h3> <input type="number" v-model="textInput"> {{ typeof textInput }} {{ textInput }} <h3>数値としてバインディング</h3> <input type="number" v-model.number="numberInput"> {{ typeof numberInput }} {{ numberInput }} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> <script> new Vue({ el: '#app', data: { textInput: '', numberInput: 0 } }); </script> </body> </html>
やっていることは、入力されたらそのデータ型と中身を表示しているだけです。
まずは通常バインディングをみてみましょう。
入力されたのは数値であるのに、取得されたデータは文字列、つまり"50"
という判断になっています。
そして、ここで重要なのはこの入力ボックスが<input type="number">
なのに数値としては取得できていないという点です。
では、v-model.number
を見てみましょう。
今度はnumber型になりました。このように明示的に数値にする方法をしっておくと思わぬエラーを回避することができるでしょう。
ただし気をつけないといけないのは、<input type="number">
にはe
という文字を入力できるということです。これは、1e+4
(=10000)のような指数表記が存在するためですが、実はこれもクセ者で、以下のような入力にしてしまうと文字列型となってしまいます。(値はなくなります)
そのため以下のようなデータ型のチェックをコード内に含めておく方が安全と言えるでしょう。
if(typeof numberInput === 'number') { // 数値です }
trimで左右の空白を取り除く
以下のようにv-model.trim
とすると、例えば xxx
と入力された場合、xxx
となります。
<input type="text" v-model.trim="trimText">
なお、うれしいことに日本語の空白も除去してくれるので安心してください。やはり開発者がIMEを使う言語の方というのがこの辺りにメリットとして出てくるのでしょうね。
今回使ったファイルをダウンロード
今回の説明で作成した全コードを以下からダウンロードすることができます。なお、Vue.jsはcdn
で利用しているので、ダウンロードしてブラウザで開くだけでコードを実行することができます。(ただし、ES6が動く環境)