Vue.jsの便利な機能coumptedとは?


前提

Vue.jsを使い始めて。便利だな〜と最近重宝しているのが算出プロパティ(computed)という仕組みです。
メソッドとは似ているようでも少し違う、computedの基本的な使い方を説明します。

算出プロパティ(computed)って何?

フォームのサンプルを使って説明します。
よくある情報登録フォームをVue.jsで作成しました。
入力される値の型チェック、未入力チェック(バリデーション)にcomputedを使っています。

実装要件

  • 各項目についてバリデーションを実装し、エラーの場合はエラーメッセージ(オレンジ文字色)を各項目部分に表示
  • 同じくエラーの場合は、Nextボタンをdisabledにする
  • 必須項目が未入力の場合もNextボタンをdisabledにする

computedの実行イメージ

項目をTelに絞って流れを図解してみます。
Telの入力値が数値ではなかったらエラーメッセージを表示するロジックです。

Telの入力値は、HTML側でinputタグにv-model="userInfo.tel"と設定しているので、JS側ではthis.userInfo.telで参照できます。これはVue.jsのフォーム入力バインディングの仕組みですね。

やりたいことは、Tel欄の入力時にバリデーションを実行したいので、ここでcomputedの出番です。
computedの仕組みは、値が変わるとその値に依存しているすべてのバインディングが更新されます。

つまりここでは、Telの値 userInfo.telが変わると、それに依存しているcomputedのvalidateTelが実行されるのです。
validateTelの返り値をみて、HTML側でエラーメッセージを表示するかどうかをv-showで設定しています


挙動を確認してみます。

Telの入力欄に入力する度にvalidateTelが実行されているのがわかるかと思います!

実装を見てみる

サンプルアプリ

ソースはこちら。(Ruby on Railsで環境を作っているためerbファイルを使っています)

index.html.erb
<div id="app" class="container">
  <h1>Form Example</h1>
  <div class="form-group">
    <label>Email [必須]</label>
    <input v-model="userInfo.email" type="email" class="form-control" placeholder="例) [email protected]">
    <span v-show="!validateEmail" class="text-warning">正しいEmailを入力してください</span>
  </div>
  <div class="form-group">
    <label>Tel [必須]</label>
    <input v-model="userInfo.tel" type="text" class="form-control" placeholder="例) 0311112222">
    <span v-show="!validateTel" class="text-warning">Telは数値で入力してください</span>
  </div>
  <div class="form-group">
    <label>ユーザー名</label>
    <input v-model="userInfo.userName" type="text" class="form-control" placeholder="例) user-1">
    <span v-show="!validateUserName" class="text-warning">ユーザー名は半角英数字で入力してください</span>
  </div>
  <button :disabled="!validation" type="button" class="btn btn-info">Next</button>
</div>

app.js
$(function () {
  new Vue({
    el: '#app',
    data: {
      userInfo: {
        email: '',
        tel: '',
        userName: ''
      }
    },
    computed: {
      validation: function () {
        return (this.validateTel &&
                this.validateEmail &&
                this.validateUserName);
      },
      validateTel: function () {
        var pattern = /^\d+$/; // 1文字以上の整数のみ許容
        return pattern.test(this.userInfo.tel.trim());
      },
      validateEmail: function () {
        var pattern = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        return pattern.test(this.userInfo.email);
      },
      validateUserName: function () {
        if (this.userInfo.userName.length === 0) {
          return true;
        } else {
          var pattern = /^\w+$/; // 大文字小文字英数字、アンダースコアのみ許容
          return pattern.test(this.userInfo.userName);
        }
      }
    }
  });
});

各項目のバリデーション用にcomputedを使い validateEmail, validateTel, validateUserNameを定義しています。
全項目のバリデーション用にそれらの返り値を返すだけのvalidationを定義しています。
このvalidationを使ってHTML側ではNextボタンのdisabledを有効にするかどうかを判定しています。

computedの使いどころって?

今回の例の他にインクリメンタルサーチ(こうゆうもの)もcomputedを使えば簡単にできるみたいです!

使いどころとしては、ユーザーからのイベント発生 => 値が変更される => その値に変更があった場合、動的に何かしらの処理を実行したい 時に使えるのではと理解しています。

算出プロパティとメソッドはどう違うの?

少し余談になりますが、computedと似たようなプロパティとしてメソッド(methods)があります。
両者の違いについては公式ガイドの算出プロパティvsメソッドがわかりやすいです。

** 以下引用 **
算出プロパティの代わりに、同じような関数をメソッドとして定義することも可能です。
最終的には、2つのアプローチは完全に同じ結果になります。

しかしながら、算出プロパティは依存関係にもとづきキャッシュされるという違いがあります。
算出プロパティは、それが依存するものが更新されたときにだけ再評価されます。

** 中略 **
対称的に、メソッド呼び出しは、再描画が起きると常に関数を実行します。
** 中略 **
キャッシングしたくない場合は、代わりにメソッドを使いましょう。

メソッドを使っても同じことができそうなので、先ほどのサンプルを編集してみます。
Telのバリデーション部分のみメソッドを使う形に実装してみました。

index.html.erb
<!-- 上記部分省略 -->
  <div class="form-group">
    <label>Tel [必須]</label>
    <input v-model="userInfo.tel" @input="validateTel2" type="text" class="form-control" placeholder="例) 0311112222">
    <span v-show="!validateTel2()" class="text-warning">Telは数値で入力してください</span>
  </div>
<!-- 以下省略 -->
app.js
<!-- 上記部分省略 -->
    computed: {
       // ここは変更なし
    },
    methods: {
      validateTel2: function () {
        var pattern = /^\d+$/; // 1文字以上の整数のみ許容
        return pattern.test(this.userInfo.tel.trim());
      }
    }
<!-- 以下省略 -->

JS側にメソッドvalidateTel2を定義し、Telのinputタグに@inputを追加しました。ユーザーがinputする度にメソッドを実行するという実装にしました。
動作しますが、このままでは他の項目(Email等)のinputタグに入力する際もこのメソッドが実行されてしまいます。。(ノ∀`*)アチャー
もう少し工夫すれば実装できそうな気もしますが、それを探るよりもcomputedを使ってサクッと実装したほうがベターですね。

まとめ

  • 算出プロパティは依存関係にもとづきキャッシュされるので、依存するものが更新される時のみ実行される。無駄な実行がない。
  • 簡潔に記載できるので、可読性が高い。

賢いcomputedを使って、さらにVue.jsを楽しみましょう ^^!

参考

公式ガイドのバリデーションサンプル