Vue.js で表示したデータの更新とイベント処理

最終更新日
2017.11.17

Vue.js で表示しているデータをイベントを使って更新します。

  • Vue 2.5.4

自分で DOM を更新しなくても良い

データを更新したら自動的に DOM も更新され今まで自分で書いていた処理を Vue.js がしてくれます。

これは Vue.js などのデータバインディング系ライブラリの大きな特徴でもあり、データと表示の矛盾やバグが発生しにくくなります。

ちなみに私が JavaScript のフレームワークをどれかやってみたいと思った一番の理由がこの機能が便利そうだったからです。

仮想 DOM についての雑学

Vue.js は最初にアプリケーションに使用するテンプレートを分解してリアル DOM と対になるようなツリー構造の仮想 DOM に展開させます。ツリーを構成するそれぞれの枝は VNode と呼ばれます。(基本トーナメント表のような図で表すので正確には木の節あたり)ユーザーの操作は基本的に仮想 DOM に対して行われ、最終的に発生した差分だけをリアル DOM に反映するので早いのです。(小並感)

イベント処理を登録する

先にイベントについて簡単に説明。

「要素にマウスが乗った時」「ボタンをクリックした時」などの イベント を利用するには v-on ディレクティブを使用します。jQuery の on と似ています。

<button v-on:click="処理内容やメソッド名">click!</button>

v-on@ で省略が可能。v-bind と同じで最初のうちは v-on で書いて慣れてきたら省略するのがオススメ。

<button @click="処理内容やメソッド名">click!</button>

修飾子を使えば prevent や自分自身の判定もできる。

<div @click.self="処理内容やメソッド名" class="overlay">...</div>

単純なテキストを更新する

普通のテキストを更新してみましょう。以下はボタンをクリックすると methods に登録してある test を実行します。処理内容はデータにある count の数を増やします。

<div id="app1">
  <p>あなたは{{ count }}回クリックしたよ</p>
  <button @click="test">click!</button>
</div>

コードを見て分かる通り DOM を更新する処理を一切していません。

var vm = new Vue({
  el: '#app1',
  data: {
    count: 0
  },
  methods: {
    test: function() {
      this.count++
    }
  }
})

this.count++ とデータを増やすだけで勝手に表示部分も増えていきます。面白いですね!

値には式が使えるので使いまわす予定のない短いコードならメソッドを用意しないで直接式を書くこともできます。あまりテンプレート内にコードを入れても見通しが悪くなるので、基本はメソッドを指定するのがいいでしょう。

<button @click="count++">click!</button>

v-model を使ってみよう

v-model はフォームとデータを紐付けるディレクティブで、紐付けたフォームの入力値や選択値とデータが常に同期するようになります。

Vue.js はコンポーネント間などデータのやりとりは基本単方向ですが、v-model はどちら側からでも自動的に同期する双方向バインディングになっています。

<div id="app2">
  <p>あなたは{{ message }}と入力したよ</p>
  <p><input type="text" v-model="inputMessage" /><button @click="getMessage">click!</button></p>
</div>
var vm = new Vue({
  el: '#app2',
  data: {
    message: '',
    inputMessage: ''
  },
  methods: {
    getMessage: function() {
      this.message = this.inputMessage
    }
  }
})

データと同期するので、もし message を直接紐付ければ、前回の試しに作ってみたサンプルと同じようにクリックしなくても入力した瞬間に message が変更されます。

ref を使ってみよう

ref を使うとテンプレートの中の要素を直接参照できます。後でやるコンポーネントタグにも使えます。基本的にはあまり使わないけど要素を直接参照したい時もあると思うので覚えておくと便利!

テンプレート内のタグに ref で名前を付けておくと this.$refs.NAME で参照できます。

<div id="app3">
  <p><input type="text" ref="message"> <button @click="getMessage">click!</button></p>
</div>
var vm = new Vue({
  el: '#app3',
  methods: {
    getMessage: function() {
      // ref="message" の入力値を使用する
      alert(this.$refs.message.value)
    }
  }
})

リストのデータを更新する

配列を更新

ちょっと頭の良くないローマ字単語リストですが、インデックス 0 番の「ringo」を大文字にするように更新してみましょう。

<div id="app4">
  <ul>
   <li v-for="val in list">{{ val }}</li>
  </ul>
  <p><button @click="test">click!</button></p>
</div>
var vm = new Vue({
  el: '#app4',
  data: {
    list: ['ringo', 'banana', 'ichigo']
  },
  methods: {
    test: function() {
      this.list[0] = this.list[0].toUpperCase()
    }
  }
})

最初何も考えずにこう書いてみたけどこれは出来ないみたいです。参考

参考 URL を読むと、JavaScript の制限で配列は直接変更出来ないから内容が変わった事を Vue が察知できるように $set メソッドを使ってねーという事です。これは監視の為に使っている JavaScript のセッター機能が配列に使えないからのようです。

vm.$set(object, key, value)

上のデータに置き換えるとこんな感じですね。メソッドの内容を書き直しましょう。

this.$set(this.list, 0, this.list[0].toUpperCase())

今度はちゃんと更新されました。

オブジェクトを更新

オブジェクトは特に滞りなく変更できました。

<div id="app6">
  <ul>
    <li v-for="(val, key) in list">(Key:{{ key }}) {{ val }}</li>
  </ul>
  <p><button @click="test">click!</button></p>
</div>
var vm = new Vue({
  el: '#app6',
  data: {
    list: {
      a: 'ringo',
      b: 'banana',
      c: 'ichigo'
    }
  },
  methods: {
    test: function() {
      this.list.a = this.list.a.toUpperCase()
    }
  }
})

ちなみに、ここに宣言していないプロパティ d を追加する時も this.$set が必要です。

オブジェクトのリストを更新

また配列なのだけどとりあえず $set を使わずに試してみましょう。

<div id="app7">
  <ul>
    <li v-for="(val, idx) in list" v-bind:key="val.name">
      (No:{{ idx }}) {{ val.name }} {{ val.count }}個
    </li>
  </ul>
  <button v-on:click="test(100, $event)">click!</button>
</div>

パラメータを持たせてインラインで実行する事もできます。この場合 $event で元のイベントオブジェクトを渡せます。

var vm = new Vue({
  el: '#app7',
  data: {
    list: [{
      name: 'りんご',
      count: 0
    }, {
      name: 'ばなな',
      count: 0
    }, {
      name: 'いちご',
      count: 0
    }]
  },
  methods: {
    test: function(count) {
      this.list[0].count = count
    }
  }
})

なんか出来ました。配列インデックスから直接弄るわけでなければ大丈夫なんですね。どうやら中のオブジェクトの監視が働いているようです。

リストのデータを v-model で更新

v-for で取得したリストのデータを直接 v-model に指定すると怒られるのでそういう場合は代わりに @input を使うのだけど、元の配列データになら紐付ける事ができます。

<div id="app8">
  <ul>
    <li v-for="(val, idx) in list" v-bind:key="idx">
      (No:{{ idx }}) {{ val }}
    </li>
  </ul>
  <ul>
    <li v-for="(val, idx) in list" v-bind:key="idx">
      (No:{{ idx }}) {{ val }} <input size="10" v-model="list[idx]">
    </li>
  </ul>
</div>
new Vue({
  el: '#app8',
  data: {
    list: ['ringo', 'banana', 'ichigo']        
  }
})

jQuery など外部からの更新に反応させるには

最初のうちはどうしても jQuery やそれに依存したプラグインを一緒に使いたい事もあるかもしれない。そんな時は JavaScript の createEventdispatchEvent を使うといいです。

<section id="app9">
  <p>{{ message }}</p>
  <input type="text" v-model="message" id="message">
  <h2>jQuery Button</h2>
  <button data-update="jQuery Text1">Text1</button>
  <button data-update="jQuery Text2">Text2</button>
  <h2>Vue Button</h2>
  <button @click="update('Vue Text1')">Text1</button>
  <button @click="update('Vue Text2')">Text2</button>
</section>
// IE を切るなら Event オブジェクトでもOK!
var eventInput = document.createEvent('UIEvents')
eventInput.initEvent('input', false, false)

$(document).on('click', '[data-update]', function() {
  $('#message').val($(this).data('update'))
  // 外部で更新したらイベントを発火させる
  $('#message')[0].dispatchEvent(eventInput)
})

new Vue({
  el: '#app9',
  data: {
    message: 'Message'
  },
  methods: {
    update(message) {
      this.message = message
    }
  }
})

それ以外にも、createdmounted 内でコールバックを設定したり、インスタンスから直接更新を知らせる事ができます。

参照のみならそんな問題はないのだけど、テーブルソートなど要素を再構築しまくるものはなるべく避けた方がいい。リストの順序を変えたりフィルタリングするには次の記事で書いている算出プロパティを使うととても簡単。

データを更新する時のハマりポイント

オブジェクトにネストされていた場合でも、配列インデックスを使って更新する場合$set を使わないと反応してくれない!どうしてもな場合以外は最後みたいにオブジェクトのリストにしてしまうのが楽かも。

おわりに

データと要素を連動させるのは jQuery で書くよりとても楽なのだけどそれでもコードが大きくなるにつれてデータの整合性を保つのが大変になってくるとよく聞きます。今では業務でも Vue を使うようになったけど大人数で開発するような大きなものを作ったことがないのであんまりイメージが出来ませんけど。そこで登場するのが Vuex らしいのですけど勉強するのはまだまだ先になりそうかな!

今回は v-on でのイベント登録の仕方とデータ更新の基本を覚えました(•ө•)ノ 次回は条件を付けて何か操作したいと思います。たぶん。