Vue.jsで簡単なフォームを作成してみた[備忘録]

## 今回作成したもの
見た目はBootstrapで作りました。
<img width="545" alt="スクリーンショット 2018-11-29 8.44.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/184969/8c6493b0-6f70-42d6-fa60-36c4a26697d2.png">

## 要件
- 作るのはフロント側のみ。内部の処理は考慮しない。
- フォームは名字、名前・電話番号・メールアドレスの4つ
- 全ての項目を入力すると送信ボタンがアクティブになる
- 電話番号とメールアドレスは入力内容の形式もチェックする
- フォームの値はリアルタイムで変更を監視

## ソースコード全体

```html:index.html
<div class="form-container container" id="app">
  <form>
    <div class="form-group">
      <label for="exampleInputEmail1">名前</label>
      <div class="form-row">
        <div class="col">
          <input v-model="form.firstName" type="text" class="form-control" placeholder="名字">
          <small v-bind:class="{'is-hide':validation.firstName}"  class="form-text text-info">{{ errorMessage.firstName }}</small>
        </div>
        <div class="col">
          <input v-model="form.lastName" type="text" class="form-control" placeholder="名前">
          <small v-bind:class="{'is-hide':validation.lastName}"  class="form-text text-info">{{ errorMessage.lastName }}</small>
        </div>
      </div>
    </div>
    <div class="form-group">
      <label for="exampleInputEmail1">電話番号</label>
      <input v-model="form.tel" type="tel" class="form-control" id="exampleInputEmail1" placeholder="電話番号">
      <small v-bind:class="{'is-hide':validation.tel}" class="form-text text-info">{{ errorMessage.tel }}</small>
    </div>
    <div class="form-group">
      <label for="exampleInputEmail1">メールアドレス</label>
      <input v-model="form.email" type="email" class="form-control" id="exampleInputEmail1" placeholder="メールアドレス">
      <small v-bind:class="{'is-hide':validation.email}" class="form-text text-info">{{ errorMessage.email }}</small>
    </div>
    <div class="btn-container"><button type="submit" v-bind:disabled="!isValid" class="btn btn-primary">送信</button></div>
  </form>
</div>

```

```javascript:main.js
var app = new Vue({
  el: '#app',
  data() {
    return {
      form: {
        firstName: '',
        lastName: '',
        tel: '',
        email: ''
      },
      errorMessage: {
        firstName: '名字を入力してください',
        lastName: '名前を入力してください',
        tel: '電話番号を入力してください',
        email: 'メールアドレスを入力してください'
      }
    }
  },

  computed: {
    validation() {
      const form = this.form
        return {
          firstName : !!form.firstName,
          lastName : !!form.lastName,
          tel : (() => {
            if(!!form.tel) {
              form.tel.replace(/[━.*‐.*―.*－.*\-.*ー.*\-]/gi,'')
              if (!form.tel.match(/^(0[5-9]0[0-9]{8}|0[1-9][1-9][0-9]{7})$/)) {
                return false
              }
              return true
            } else {
              return false
            }
          })(),
          email : (() => {
            if(!!form.email) {
              if (!form.email.match(/^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]{1,}\.[A-Za-z0-9]{1,}$/)) {
                return false
              }
              return true
            } else {
              return false
            }
          })()
        }
      },
      isValid() {
        var validation = this.validation
        return Object.keys(validation).every(function (key) {
          return validation[key]
      })
    }
  }
})

```

参考：[電話番号の正規表現をJAVASCRIPTを使ってチェック](https://its-office.jp/blog/js/2017/08/19/telmatch.html)
参考：[JavaScript正規表現の使用例：メールアドレスのバリデーション](https://lab.sonicmoov.com/development/javascript/javascript-regex-sample/)
参考：[[Vue.js] Bootstrap と Vue.js でイケてるフォームを実装する](https://atuweb.net/201704_vue-bootstrap-form/)

## ポイント
### v-modelによる双方向データバインディング
```html
<input v-model="form.firstName" type="text" class="form-control" placeholder="名字">
```
対象要素に「v-model」とつけるだけで簡単に双方向データバインディングが実現できます。
これでリアルタイムにフォームの内容を確認できるようになりました。

### 算出プロパティ：validation

```javascript
validation() {
  const form = this.form
  return {
    firstName : !!form.firstName,
    lastName : !!form.lastName,
    tel : (() => {
      if(!!form.tel) {
        form.tel.replace(/[━.*‐.*―.*－.*\-.*ー.*\-]/gi,'')
        if (!form.tel.match(/^(0[5-9]0[0-9]{8}|0[1-9][1-9][0-9]{7})$/)) {
          return false
        }
        return true
      } else {
        return false
      }
    })(),
    email : (() => {
      if(!!form.email) {
        if (!form.email.match(/^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]{1,}\.[A-Za-z0-9]{1,}$/)) {
          return false
        }
        return true
      } else {
        return false
      }
    })()
  }
}
```

フォームのバリデーションをする算出プロパティです。
各フォームの入力状況の真偽値を含んだオブジェクトが返ってきます。
validation."フォーム名"で各フォームの状態を取得出来ます。

```javascript
<small v-bind:class="{'is-hide':validation.firstName}"  class="form-text text-info">{{ errorMessage.firstName }}</small>
```
真偽値によってクラス付け外しをするのに使っています。


算出プロパティはフォームの値が変わるたびに再計算されます。これによりリアルタイムにデータのバリデートが実現できます。
無駄な処理を書かずに実装できるのが良いですね。
（最初はfilterでやろうと思ったのですが、2.x系ではmustache展開とv-bind以外使えないようです）

#### フォームのバリデーション 

```javascript
tel : (() => {
  if(!!form.tel) {
    form.tel.replace(/[━.*‐.*―.*－.*\-.*ー.*\-]/gi,'')
    if (!form.tel.match(/^(0[5-9]0[0-9]{8}|0[1-9][1-9][0-9]{7})$/)) {
      return false
    }
    return true
  } else {
    return false
  }
}(),
```
電話番号の部分をピックアップ。
returnする前の部分でバリデーションをしています。
処理の流れはシンプルですが、見慣れない記述が多く戸惑いました。

#### アロー関数での即時関数
```javascript
(() => {
  //処理内容
})();
```
無名関数を宣言して即実行しています。
アロー関数を用いた即時関数の記述は覚えておきたいです。
括弧が多くて混乱しやすいです。

#### 二重否定の論理演算子

```javascript
firstName : !!form.firstName,
lastName : !!form.lastName,
```

コードの中に何度か「!!」という記述が出てきました。
こちらは二重否定の論理演算子というものです。否定の意味である「!」は見ますが「!!」は見慣れなくて焦りました。
否定を否定するので、
!!true = true
!!false = false
です。

どうやら古いブラウザではundefinedに対応しておらず、boolean型として認識させるためにこのような書き方をしているようです。
参考：[【JavaScript】!!（ビックリマーク2つ）って何？](https://senews.jp/bikkuri-2/)

### 算出プロパティ：isValid
```javascript
isValid() {
  var validation = this.validation
  return Object.keys(validation).every(function (key) {
    return validation[key]
})
```

全てのフォームの入力内容を見ている部分です。全て入力されていればtrue、そうでなければfalseが返ってきます。

```javascript
<button type="submit" v-bind:disabled="!isValid" class="btn btn-primary">送信</button>
```
この真偽値が送信ボタンの状態に紐づいています。
1つずつ分解していきます。

#### Object.keys（）
指定されたオブジェクトが持つプロパティのnamesを上から順番に返えします。

#### every（）
配列が条件を全て満たす場合trueを返す。
これでフォームの内容全てが正しく入力されているかを確認しています。

2つを組み合わせて全てのフォームの入力状態を確認している事がわかります。
これで最初に上げた要件を満たすフォームを作る事ができました。

## 最後に
パッと見て出来そうだと思っても実際に作ってみると思ってたよりも出来ないし時間がかかります。
1つ1つ分解して理解して自分の引き出しを増やして業務の何処かでいければなと思います。