Vue 2 | 表示データの変更とイベント処理

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>

修飾子を使えるようになると、preventselfのような判定も可能になる。

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

簡単なテキストを変更する

簡単なテキストを変更してみよう。次の例では、ボタンをクリックするとmethodsに定義されたtestメソッドを呼び出す。処理内容は、dataに定義されているcountプロパティの値を増やすことである。

コード実行

<div id="app" class="area">
    <p>あなたは{{ count }}回クリックしました。</p>
    <button v-on:click="test">click!</button>
</div>

DOMを変更する処理はない。

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

this.count++count値を増やすと、表示部分も一緒に更新される。

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

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

コード実行

v-modelを使う

v-modelはフォーム入力とデータを結び付けるディレクティブで、接続されたフォームの入力値や選択値とデータを常に同期できる。

コード実行

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

データと同期しているため、messageを直接結び付けると、上の例のようにクリックしなくても入力した瞬間にmessageが変更される。

$refsを使ってみる

$refsを使うとDOMを直接参照できる。後で学ぶコンポーネントタグにも使用できる。注意点は、これは仮想DOMを使わないためレンダリング最適化が行われないことである。何かを操作するたびにDOMへアクセスして再描画するため、描画更新には向いていない。基本的に多用はしないが、要素を参照したい場合もあるので覚えておこう。

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

コード実行

<div id="app">
  <p><input type="text" ref="message"> <button @click="getMessage">click!</button></p>
</div>
new Vue({
  el: '#app',
  methods: {
    getMessage: function() {
      alert(this.$refs.message.value)
    }
  }
})

要素の位置や高さなど、直接DOMへアクセスする必要がある場合に使う。

リストデータの更新

配列の更新

用意された英単語リストのうち、インデックス0のappleを大文字に変更してみる。

new Vue({
  el: '#app4',
  data: {
    list: ['apple', 'banana', 'strawberry']
  },
  methods: {
    test: function() {
      this.list[0] = this.list[0].toUpperCase()
    }
  }
})

このように書いても動作しない。JavaScriptの制限により配列を直接変更してもVueが検知できないため、$setメソッドを使用する。

vm.$set(object, key, value)

このデータ置換は次のように書く。

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

コード実行

今回は正しく変更された。この仕様はIE対応の影響が大きい。将来のVue.jsではIEサポートがなくなり、Vue.setが不要になるかもしれない。

オブジェクトの変更

オブジェクトは問題なく変更できる。

new Vue({
  el: '#app',
  data: {
    list: {
      a: 'apple',
      b: 'banana',
      c: 'strawberry'
    }
  },
  methods: {
    test: function() {
      this.list.a = this.list.a.toUpperCase()
    }
  }
})

コード実行

ただし、もともと存在しないプロパティを追加する場合はthis.$setが必要である。

オブジェクトリストの変更

配列でも、インデックスを直接置き換えなければ動作するようである。

<div id="app">
  <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で元のイベントオブジェクトを渡せる。

new Vue({
  el: '#app',
  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に指定するのは避けるべきだが、元が配列データであれば接続できる。

<div id="app">
  <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>

コード実行

jQueryのような外部変更に対応する

最初はjQueryのようなプラグインを一緒に使いたいこともある。その場合はJavaScriptのdispatchEventを使う。

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

参照だけなら問題は少ないが、テーブルソートのように要素を再構成する処理はなるべく避けたほうがよい。実DOMを操作してもデータが更新されるわけではなく、場合によっては仮想DOMによって修正されるためである。Vue.jsでは主役はDOMではなくデータである。リストの順序変更やフィルタリングは、次章で紹介する算出プロパティを使うと簡単にできる。

データ更新で忘れがちな点

オブジェクトへ順番に追加する場合でも、配列インデックスを使って更新する場合は$setを使わないと反応しない。必要な場合以外は、最後にオブジェクトリストを更新する形にすると楽かもしれない。

結論

データと要素を連動させるのはjQueryで書くよりかなり便利だが、コードが大きくなるとデータの整合性を保つのが難しくなる。その補完として登場したのがVuexなのだろう。

今回はv-onによるイベント登録方法とデータ更新の基本を学んだ。次は条件分岐による操作を見ていく。