基礎からメモ: Vue.js CH4-p120 算出プロパティの使い方

算出プロパティ

算出プロパティはcomputedプロパティに定義した関数で任意のデータを処理するもの。

widthプロパティの値の半分を割り出して、その値を返すhalfWidth関数を算出プロパティとして定義。

var app = new Vue({
  el: '#app',
  data: {
    width: 800
  },
  computed: {
    halfWidth: function() {
      return this.width / 2;
    }
  }
})

HTML側からは算出プロパティに定義した関数名を、その関数が返す値として使えるので、あたかもhalfWidthというプロパティがあるかのように見える。これでテンプレートに複雑な式を書かなくてすむ。

<div id="app">
  <p>{{ width }} の半分は {{ halfWidth }}</p>
</div>

算出プロパティを組合せる

算出プロパティとしてインスタンスcomputedオプションに定義した別の関数halfWidthhalfHeightを使ったもう一つの算出プロパティhalfPointで、中心ポイントを表すオブジェクトを返す。

<div id="app">
  <p>X: {{ halfPoint.x}}</p>
  <p>Y: {{ halfPoint.y}}</p>
</div>
var app = new Vue({
  el: '#app',
  data: {
    width: 800,
    height: 600
  },
  computed: {
    halfWidth: function() {
      return this.width / 2;
    },
    halfHeight: function() {
      return this.height / 2;
    },
    // width x height の中心座標
    halfPoint: function() {
      return {
        x: this.halfWidth,
        y: this.halfHeight
      }
    }
  }
})

結果表示

<div id="app">
  <p>X: 400</p>
  <p>Y: 300</p>
</div>

ゲッターとセッター

算出プロパティにゲッターやセッターを定義することができる。両方定義することで、データを操作するとどちらからでも同期できる(双方向フロー)。 ゲッターはget、セッターはsetという名前の関数を、算出プロパティの関数内にオブジェクトとして定義するだけである。

ゲッターに紐付けたinputに入力すれば、データのwidthを変更でき、同時にその半分のデータも変わる。半分のデータを入力すればwidthデータもその倍になる。

<div id="app">
  <input v-model.number="width"> {{ width }}
  <input v-model.number="halfWidth"> {{ halfWidth }}
  <p>{{ width}}</p>
  <p>{{ halfWidth}}</p>
</div>
var app = new Vue({
  el: '#app',
  data: {
    width: 800
  },
  computed: {
    halfWidth: {
        get: function() { return this.width / 2 },
        set: function(val) { this.width = val * 2 }
      },
    }
});

算出プロパティのキャッシュ機能

算出プロパティはリアクティブなデータにのみ依存してキャッシュを構築、再構築する。算出プロパティ内で使われるデータがリアクティブではない場合、再構築されない。それに対して、メソッドが返すデータはその都度実行されるため、呼び出されるたびに再構築される。

次のように算出プロパティとメソッドの両方に乱数を返す操作が2箇所づつある場合、メソッドの返す数値は描画のたびに2箇所の数値が違ったものになるが、算出プロパティが返す乱数はリアクティブデータではないため2箇所とも同じ数値が返る。

<div id="app">
  <!-- 算出プロパティ -->
  <ol>
    <li>{{ computedData }}</li>
    <li>{{ computedData }}</li>
  </ol>
  <!-- メソッド -->
  <ol>
    <li>{{ methodsData() }}</li>
    <li>{{ methodsData() }}</li>
  </ol>
</div>
var app = new Vue({
  el: '#app',
  computed: {
    computedData: function() { return Math.random(); }
  },
  methods: {
    methodsData: function() { return Math.random(); }
  }
});

リストの絞り込み

算出プロパティのキャッシュ機能によって、複雑な処理内で更新の無いデータを再利用し負荷を軽減できる。

リストから「◯円以下」の商品を「◯件」表示させる例。limitの数値を変更してもbudgetが変わらなければmatchedの計算結果が使われる。

<div id="app">
  <input v-model.number="budget"> 円以下に絞り込む
  <input v-model.number="limit"> 件を表示
  <p>{{ matched.length }} 件中 {{ limited.length }} 件を表示中
  <ul>
    <!-- v-forでは最終結果、算出プロパティのlimitedを使用する -->
    <li v-for="item in limited" v-bind:key="item.id">
      {{ item.name }} {{ item.price }}円
    </li>
  </ul>
</div>
var app = new Vue({
  el: '#app',
  data: {
    budget: 500,
    limit: 5,
    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: {
    matched: function() {
      return this.list.filter(function(el) {
        return el.price <= this.budget;
      }, this)
    },
    limited: function() {
      return this.matched.slice(0, this.limit);
    }
  }
});

ソート機能の実装

javascriptsortは配列を直接操作する。そのため算出プロパティ内で使用すると元のデータが変わってしまう。それを防ぐためにシャローコピーを使用したりLodashなどのライブラリを使う方法がある。

this.list.slice(0).sort();

先のリスト絞り込みにソート機能のあるボタンを追加してみる。

<body>
  <div id="app">
    <input v-model.number="budget"> 円以下に絞り込む
    <input v-model.number="limit"> 件を表示
    <p>{{ matched.length }} 件中 {{ limited.length }} 件を表示中
    <ul>
      <!-- v-forでは最終結果、算出プロパティのlimitedを使用する -->
      <li v-for="item in limited" v-bind:key="item.id">
        {{ item.name }} {{ item.price }}円
      </li>
    </ul>
    <button v-on:click="order=!order">切り替え</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script>
  <script src="main.js"></script>
</body>

matchedlimitedの間にソート処理関数sortedを定義する。

[main.js]

var app = new Vue({
  el: '#app',
  data: {
    budget: 500,
    limit: 5,
    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 },
    ],
    order: false
  },
  computed: {
    matched: function() {
      return this.list.filter(function(el) {
        return el.price <= this.budget;
      }, this)
    },
    sorted: function() {
      return _.orderBy(this.matched, 'price', this.order ? 'desc' : 'asc');
    },
    limited: function() {
      // limited で使うデータを matched から sorted に変更
      return this.sorted.slice(0, this.limit);
    }
  },
});

切り替えボタンはorderプロパティを逆転させているだけだが、リアクティブデータのorderが変化するので、sortedlimitedの算出が行われる。