7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Vue.js - 4】データの監視と加工

Last updated at Posted at 2019-06-07

まとめ

・算出プロパティは結果をキャッシュしてくれる
・データの状態に対して処理をしたいならウォッチャ
・カスタムディレクティブは監視をしながらDOMの操作ができる
・更新後のDOMにアクセスしたいならnextTick

1. 算出プロパティで処理を含むデータを作成

算出プロパティ(Computed 処理を含むデータ)

html
<p>{{ width }} の半分は {{ halfWidth }}</p>
Vue.js
new Vue({
  el: '#app',
  data: {
    width: 800
  },
  computed: {
    //算出プロパティhalfWidthを定義
    halfWidth: function() {
      return this.width / 2
    }
  }
})

算出プロパティの組み合わせ

html
<p>X: {{ halfPoint.x }}</p>
<p>Y: {{ halfPoint.y }}</p>
Vue.js
new Vue({
  el: '#app',
  data: {
    width: 800,
    height: 600
  },
  computed: {
    halfWidth: function() {
      return this.width / 2
    },
    halfHeight: function() {
      return this.height / 2
    },
    // 「width × height」の中心座標をオブジェクトで返す
    halfPoint: function() {
      return {
        x: this.halfWidth,
        y: this.halfHeight
      }
    }
  }
})

ゲッターとセッター

html
<input v-model.number="width"> {{ width }}
<input v-model.number="halfWidth"> {{ halfWidth }}
Vue.js
new Vue({
  el: '#app',
  data: {
    width: 800
  },
  computed: {
    halfWidth: {
      get: function() {
        return this.width / 2
      },
      //halfWidth の2倍の数値を width に代入する
      set: function(val) {
        this.width = val * 2
      }
    }
  }
})

算出プロパティのキャッシュ機能
算出プロパティ…リアクティブな依存データに基づき、結果をキャッシュする。キャッシュを再構築するトリガになるのはリアクティブなデータのみ
メソッド…キャッシュされない

html
<!-- 算出プロパティ:キャッシュされるため、何度使用しても同じ結果 -->
<ol>
  <li>{{ computedData }}</li><!-- 0.8205563271901375 -->
  <li>{{ computedData }}</li><!-- 0.8205563271901375 -->
</ol>

<!-- メソッド:キャッシュされない。呼び出すたび変わる -->
<ol>
  <li>{{ methodsData() }}</li>0.13761479619266637
  <li>{{ methodsData() }}</li>0.45429414765683895
</ol>
Vue.js
new Vue({
  el: '#app',
  computed: {
    computedData: function() { return Math.random() }
  },
  methods: {
    methodsData: function() { return Math.random() }
  }
})

サンプルコード:リストの絞り込み・ソート機能

html
<div id="app">
  <input v-model.number="budget"> 円以下に絞り込む
  <input v-model.number="limit"> 件を表示
  <button v-on:click="order=!order">切り替え</button>
  <p>{{ matched.length }} 件中 {{ limited.length }} 件を表示中</p>
  <ul>
    <!-- v-forでは最終結果、算出プロパティのlimitedを使用する -->
    <li v-for="item in limited" v-bind:key="item.id">
      {{ item.name }} {{ item.price }}円
    </li>
  </ul>
</div>
Vue.js
new Vue({
  el: '#app',
  data: {
    //フォームの入力と紐付けるデータ
    budget: 300,
    //表示件数
    limit: 2,
    //もとになるリスト
    list: [
      { id: 1, name: 'りんご', price: 100 },
      { id: 2, name: 'ばなな', price: 200 },
      { id: 3, name: 'いちご', price: 400 },
      { id: 4, name: 'おれんじ', price: 300 },
      { id: 5, name: 'めろん', price: 500 }
    ]
  },
  computed: {
    //budget以下のリストを返す算出プロパティ
    matched: function () {
      return this.list.filter(function (el) {
        return el.price <= this.budget
      }, this)
    },
    //matchedで返ったデータをlimit件返す算出プロパティ
    limited: function () {
      return this.matched.slice(0, this.limit)
    }
  }
})

##2. ウォッチャでデータを監視して処理を自動化する

ウォッチャ

Vue.js
new Vue({
   ...
  watch: {
    監視するデータ: function (新しい値, 古い値) {
       valueが変化したときに行いたい処理
    },
    'item.value': function (newVal, oldVal) {
       //オブジェクトのプロパティも監視できる
    }
  }
})

ウォッチャ(オプションあり)

Vue.js
new Vue({
   ...
  watch: {
    list: {
      handler: function (newVal, oldVal) {
         //listが変化したときに行いたい処理
      },
      deep: true,//ネストされたオブジェクトも監視するか
      immediate: true //初期読み込み時にも呼び出すか
    }
  }
})

インスタンスメソッドでの登録(オプションなし)

Vue.js
this.$watch(監視するデータ, ハンドラ, オプション任意)
this.$watch('value', function(newVal, oldVal) {
   //...
})

インスタンスメソッドでの登録(オプションあり)

Vue.js
this.$watch('value', function (newVal, oldVal) {
   ...
}, {
  immediate: true,
  deep: true
})

一度だけ動作するウォッチャ(unwatchで解除する)

Vue.js
new Vue({
  el: '#app',
  data: {
    edited: false,
    list: [
      { id: 1, name: 'りんご', price: 100 },
      { id: 2, name: 'ばなな', price: 200 },
    ]
  },
  created: function() {
    var unwatch = this.$watch('list', function () {
      //listが編集されたことを記録する
      this.edited = true
      //監視を解除
      unwatch()
    }, {
      deep: true
    })
  }
})

実行頻度の制御p130
フォームの入力などによって監視する値が高頻度で変化する→ウオッチャの実行頻度を制御する

html
<!-- ※lodash.min.jsを読み込む -->
<input type="text" v-model="value">
Vue.js
new Vue({
  el: '#app',
  data: {
    value: '編集してみてね'
  },
  watch: {
    //実行から指定ミリ秒がすぎた場合にコールバックを呼び出す
    value: _.debounce(function (newVal) {
       //ここへコストの高い処理を書く
      },
      //valueの変化が終わるのを待つ時間をミリ秒で指定
      500)
  }
})

複数の値を監視する

Vue.js
//インスタンスメソッドを使い監視対象を関数にして登録
 this.$watch( function() {
   return [this.width, this.height]
 }, function() {
    //widthまたはheightが変化した時の処理
 })
//オプションに登録する場合は、算出プロパティを監視する
 cpmputed: {
   watchTarget: function() {
     return [this.width, this.height]
   }
 },
 watch: {
   watchTarget: function() { ... }
 }

オブジェクト型の古い値との比較方法 p132

フォームを監視してAPIからデータを取得(検索フォームに応用できる)

html
<!-- ※axios.min.jsを読み込む -->
<div id="app">
  <select v-model="current">
    <option v-for="topic in topics" v-bind:value="topic.value">
       {{ topic.name }}
    </option>
  </select>
  <div v-for="item in list">{{ item.full_name }}</div>
</div>
Vue.js
new Vue({
  el: '#app',
  data: {
    list: [],
    current: '',
    topics: [
      { value: 'vue', name: 'Vue.js' },
      { value: 'jQuery', name: 'jQuery' }
    ]
  },
  watch: {
    current: function (val) {
      //GitHubのAPIからトピックのリポジトリを検索
      axios.get('https:api.github.com/search/repositories', {
        params: {
          q: 'topic:' + val
        }
      }).then(function (response) {
        this.list = response.data.items
      }.bind(this))
    }
  },
})

3. フィルタの使い方

html
<!-- Mustacheで使用する場合 -->
<div>{{ 対象のデータ | フィルタの名前 }}</div>
<!-- v-bindで使用する場合 -->
<div v-bind:id=" 対象のデータ | フィルタの名前 "></div>

ローカルでフィルタを使う

html
<p>{{ price | localNum }}円</p>
Vue.js
new Vue({
  el: '#app',
  data: {
    price: 19800
  },
  filters: {
    localeNum: function (val) {
      return val.toLocaleString()
    }
  }
})
//グローバルでフィルタを使う(全てのコンポーネントから使用できる)
Vue.filter('localNum', function(val) {
  return val.toLocalString()
})

フィルタに引数を持たせる

html
<p>{{ message | filter(foo, 100) }}</p>
Vue.js
new Vue({
  filters: function(message, foo, num) {
    console.log(message, foo, num)
  }
})

複数のフィルタを繋げて使用

html
<p>180 度は {{ 180 | radian | round }} ラジアンだよ</p>
Vue.js
new Vue({
  el: '#app',
  filters: {
    //小数点以下を第2位に丸めるフィルタ
    round: function (val) {
      return Math.round(val * 100) / 100
    },
    //度からラジアンに変換するフィルタ
    radian: function (val) {
      return val * Math.PI / 180
    }
  }
})

4. カスタムディレクティブ

コンポーネントのdirectivesオプションに登録することで、特定のコンポーネント内で使用できる

html
<input type="text" v-focus>
Vue.js
new Vue({
  el: '#app',
  directives: {
    focus: {
      //紐付いている要素がDOMに挿入されるとき
      inserted: function (el) {
        el.focus()  //要素にフォーカスを当てる
      }
    }
  }
})
// グローバルへの登録
Vue.directive('focus', {
  inserted: function(el){
    el.focus()
  }
})

使用可能なフック

Vue.js
Vue.directive('example', {
  //ディレクティブが初めて要素と紐づいた時
  bind: function (el, binding) {
    console.log('v-example bind')
  },
  //紐づいた要素が親Nodeに挿入された時
  inserted: function (el, binding) {
    console.log('v-example inserted')
  },
  //紐付いた要素を包含しているコンポーネントのVNodeが更新された時
  update: function (el, binding) {
    console.log('v-example update')
  },
  //包含しているコンポーネントと子コンポーネントのVNodeが更新された時
  componentUpdated: function (el, binding) {
    console.log('v-example componentUpdated')
  },
  //紐付いていた要素からディレクティブから削除される時
  unbind: function (el, binding) {
    console.log('v-example unbind')
  }
})

//updateおよびcomponentUpdate:
//コンポーネントの仮想DOMが更新された時に呼び出される

フックの引数

引数 作用
el ディレクティブが付与されている要素
binding バインドされた値、引数、修飾子のオブジェクト
vnode 要素に対応するVNode
oldVnode 更新前のVNode(updateおよびcomponentUpdatedのみ使用可)

フックの関数による省略記法
第2引数として関数を渡す:bindとupdateにフックされる

Vue.js
Vue.directive('example', function(el, binding, vnode, oldVnode) {
  //bindとupdateで呼び出される処理
})

例)動画の再生を操作する

html
 <div id="app">
   <button v-on:click="video1=true">再生</button>
   <button v-on:click="video1=false">再生</button>
   <video src="movie1.mp4 v-video="video1">再生</video>
   <button v-on:click="video2=true">再生</button>
   <button v-on:click="video2=false">再生</button>
   <video src="movie1.mp4 v-video="video2">再生</video>
 </div>
Vue.js
new Vue({
  el: '#app',
  data: {
    video1: false,
    video2: false
  },
  directives: {
    video(el, binding) {
      if (binding.value !== binding.oldValue) {前の処理と比較して処理を行う
        binding.value ? el.play() : el:pause()
      }
    }
  }
})

第2引数bindingは次のプロパティを含むオブジェクトになる

1 2
arg 引数
modifiers 修飾子のオブジェクト
value 新しい値
oldValue 古い値(updateおよびcomponentUpdatedのみ使用可)

5. nextTickで更新後のDOMにアクセスする

html
<button v-on:click="list.push(list.length+1)">追加</button>
<ul ref="list">
  <li v-for="item in list">{{ item }}</li>
</ul>
Vue.js
new Vue({
  el: '#app',
  data: {
    list: []
  },
  watch: {
    list: function () {
      //更新後のul要素の高さを取得できない…
      console.log('通常:', this.$refs.list.offsetHeight)
      //nextTickを使えばできる!
      this.$nextTick(function () {
        console.log('nextTick:', this.$refs.list.offsetHeight)
      })
    }
  }
})
7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?