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

最終更新日
2018.04.15

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

  • Vue 2.5.4

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

データを更新したら自動的に DOM も更新され今まで自分で書いていた処理を Vue.js がしてくれます。 これは Vue.js などのデータバインディング系ライブラリの大きな特徴でもあり、データと表示の矛盾や見た目のバグが起きにくくなります。 ちなみに私が JavaScript のフレームワークをどれかやってみたいと思った一番の理由がこの機能が便利そうだったからです。

仮想 DOM についての雑学

Vue.js は最初にアプリケーションに使用するテンプレートを分解してリアル DOM と対になるようなツリー構造の仮想 DOM に展開させます。 ツリーを構成するそれぞれの要素は VNode と呼ばれます。 データを更新してもすぐには DOM を更新せずに、仮想 DOM を通して同期処理の結果を、非同期にリアル DOM に反映するので無駄な DOM 操作がなく早いのです。(小並感)

非同期に 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++ とデータを増やすだけで勝手に表示部分も増えていきます。 面白いですね!

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

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

v-model を使ってみよう

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 が更新されます。

$refs を使ってみよう

$refs を使うと直接 DOM を参照できます。 またあとで勉強するコンポーネントタグにも使えます。 注意点としてこれは仮想 DOM を介さないので描画の最適化をしません。 何か操作するたびに DOM にアクセスして再描画するので、描画の更新に使うには向いていません。 基本的にはあまり使わないけど要素を参照したい時もあると思うので覚えておくと便利!

テンプレート内のタグに 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)
    }
  }
})

要素の位置や高さなど直接 DOM にアクセスしなければ分からないものもあるため、そういったケースで使用します。

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

配列を更新

ちょっと頭の良くないローマ字単語リストですが、インデックス 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 メソッドを使ってねーという事です。

vm.$set(object, key, value)

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

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

今度はちゃんと更新されました。 まぁ、この仕様はいつもどおり IE のせいなので、時期バージョン(Vue.js3?)では IE のサポートを切り、Vue.setは不要になるようです。

オブジェクトを更新

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

<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>

v-onのハンドラは、パラメータを持たせてインラインで実行する事もできます。 この場合 $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 dispatchEvent を使うといいです。

<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
    }
  }
})

参照のみならそんな問題はないのだけど、テーブルソートなど要素を再構築しまくるものはなるべく避けた方がいいでしょう。 リアル DOM を操作してもデータを更新しているわけではないので、場合によって仮想 DOM によって上書きされるためです。 Vue.js では主役は DOM ではなくデータ!ということは重要なポイントです。 リストの順序を変えたりフィルタリングするには、次の記事で書いている算出プロパティを使うととても簡単。

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

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

おわりに

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

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