Vue.jsでフォームバリデーションを作ってみよう！

# はじめに
こちらは[サポーターズColab](https://supporterzcolab.com/) で開催の[勉強会](https://supporterzcolab.com/event/856/)の説明資料です。

----
## この記事に書いてあること
- [VueCLI](https://cli.vuejs.org/) を使った環境構築の手順
- コンポーネントの作り方
- ボタンをクリックしたときのフォームバリデーション実装

## この記事で省いていること
- ツールや構成ファイルの説明
- コードのリファクタリング

----
# 環境
- 端末
	- Mac OS X 10.14.5
- インストール済みのライブラリ
	- [node.js](https://nodejs.org/ja/)
		- v12.4.0
	- [npm](https://www.npmjs.com/)
		- 6.9.0
		
----

# 開発環境のインストール

Vue.jsの開発環境の構築方法として、下記の2つがあります。

- `script` で直接埋め込む
    - 個人開発向け
    - 表示部分を`.html`、`.css`、`.js`ファイルで作成
- `‌VueCLI` 使ってインストール
    - 中規模以上の開発向け
    - 表示部分を`.vue`ファイルで作成

今回は`.vue`ファイルを使いたいので、`‌VueCLI`を使っていきます。
インストール手順は、[VueCLIの公式サイト](https://cli.vuejs.org/)にしたがって進めていきます。

----

## vue-cliのインストール
下記コマンドを実行します。

``` shell:shell
$ npm install -g @vue/cli
```

インストールが完了すると下記の表示されます。

```shell:shell
+ @vue/cli@3.8.2
added 877 packages from 554 contributors in 60.837s
```
----

#### VueCLIの `2.x` がインストールされていて、`3.x`に変更したい方
今のバージョンをアンインストールの上、再度インストールをお願いします。

``` shell:shell
$ npm uninstall -g vue-cli
$ npm install -g @vue/cli
```

----

## プロジェクトを作成

```shell:shell
$ vue create my-project
```

`my-project` の部分はファイル名になります。
自分が管理しやすい名称をつけてください。

※コマンドを実行した際に下記が出てくることがあります。

```
Your connection to the default npm registry seems to be slow.
Use https://registry.npm.taobao.org for faster installation? (Y/n)
```

特に急ぎでない場合は `n` を入力します。

----

### プリセットの選択

```
Vue CLI v3.2.1
┌───────────────────────────┐
│  Update available: 3.8.2  │
└───────────────────────────┘
? Please pick a preset: (Use arrow keys)
❯ default (babel, eslint)
  Manually select features
```

プリセットに関して今回は `default (babel, eslint)` を選択します。

### パッケージマネージャーの選択
```
? Pick the package manager to use when installing dependencies: (Use arrow keys)
❯ Use Yarn
  Use NPM
```
特にこだわりがなければ `Use NPM` を選択します。
※表示されない場合もあります。

インストールが開始され、完了すると、下記が表示されます。

```
🎉  Successfully created project my-project.
```
----

## ローカル環境を立ち上げる
インストール完了後に表示されていたコマンドを実行します。

```
👉  Get started with the following commands:

 $ cd my-project
 $ npm run serve
```

立ち上げが成功すると、下記が表示されます。

```
DONE  Compiled successfully in 3538ms                                0:00:00 PM

  App running at:
  - Local:   http://localhost:8080/
  - Network: http://0.0.0.0:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.
```

[http://localhost:8080](http://localhost:8080) にアクセスし、表示ができていれば、環境構築は完了です。

![vue-hands-on-001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186755/f89110cf-9b6f-9eb3-d5bc-a1df3758ca40.png)

----

# フォームバリデーションを作る
ここから実際の開発を進めていきます。

----

## 単一ファイルコンポーネントの記述方法
開発に入る前に単一ファイルコンポーネントの構成について説明します。
単一ファイルコンポーネントは、 `<template>`、`<script>`、`<style>` のタグで構成されています。

``` HTML:.vue
<template>
<!-- HTMLの記述はこちら -->
</template>
<script>
export default {
  // JavaScriptの記述はこちら
}
</script>
<style scoped>
/* CSSの記述はこちら */
</style>
```

各タグの役割については下記の通りです。

#### template
HTMLの記述箇所。
HTMLのテンプレートエンジンのPUGを導入することも可能。

#### script
JavaScriptの記述箇所。
TypeScirptを導入することも可能。

#### style
`scoped` をつけることで、styleをコンポーネント内に閉じ込めることができる。
PostCSSやLess、Sass、Scss、Stylusを導入することも可能。

単一コンポーネントのタグの順序に関しては、[公式ガイドの単一ファイルコンポーネントのトップレベルの属性の順序](https://jp.vuejs.org/v2/style-guide/index.html#%E5%8D%98%E4%B8%80%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E3%83%88%E3%83%83%E3%83%97%E3%83%AC%E3%83%99%E3%83%AB%E3%81%AE%E5%B1%9E%E6%80%A7%E3%81%AE%E9%A0%86%E5%BA%8F-%E6%8E%A8%E5%A5%A8)を参照ください。

----

## コンポーネントを作成
`src/components/` の配下に `LoginForm.vue` をファイルを作成し、下記を記述します。

``` HTML:src/components/LoginForm.vue
<template>
<div>
  <p><b>{{title}}</b></p>
</div>
</template>

<script>
export default {
  props: {
      title : String
  }
}
</script>

<style scoped>
</style>
```

#### template 
`script`で宣言した `title` を表示できるよう記述

#### script
`src/App.vue` のテンプレートで `src/components/LoginForm.vue` を使用するため、`App.vue`が親、`LoginForm.vue` が子という関係になります。

Vue.jsは[単方向のデータフロー](https://jp.vuejs.org/v2/guide/components-props.html#%E5%8D%98%E6%96%B9%E5%90%91%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%83%95%E3%83%AD%E3%83%BC)のため、親と子の間でデータをやり取りする際には、宣言が必要です。

親から子へデータを渡す際には`props`を用い、 子から親データを渡すときは`カスタムイベント`を用います。

- `props`の詳細は、[公式ガイドのprops](https://jp.vuejs.org/v2/api/#props)を参照
- `カスタムイベント`の詳細は、[公式ガイドのカスタムイベント](https://jp.vuejs.org/v2/guide/components-custom-events.html#ad)を参照

----

## App.vueからコンポーネントを使用
`‌src/App.vue` から `src/components/LoginForm.vue` を呼び出します。

``` HTML:src/App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <!-- ここから追加 -->
    <hr>
    <LoginForm title="ログインフォーム"></LoginForm>
    <hr>
    <!-- ここまでを追加 -->
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'
import LoginForm from './components/LoginForm.vue'// 追加

export default {
  name: 'app',
  components: {
    HelloWorld,
    LoginForm // 追加
  },
}
</script>

<style>
/* 省略 */
</style>
```

#### template
`script`で登録したコンポーネントを `LoginForm` をタグとして使用します。
`title` に`ログインフォーム`という文字列を親から子に渡しています。

#### script
`components`のオプション内に`LoginForm`をコンポーネントとして定義しています。

----

## フォーム入力バインディングを作成
`src/components/LoginForm.vue` にフォームを作り、入力した値をバインディングする処理を追加します。

``` HTML:src/components/LoginForm.vue
<template>
<div>
  <p><b>{{title}}</b></p>
  <!-- ここから追加 -->
  <form>
    <span>ID : </span>          
    <input
      type="text"
      placeholder="input your id"
      v-model="loginForm.loginId"
    >
      <p>
        Input loginId is {{ loginForm.loginId }} 
      </p>
    </form>
    <!-- ここまでを追加 -->
</div>
</template>

<script>
export default {
  props: {
      title : String
    },//ここにカンマを追加
  //ここから追加
  data: function() { 
    return {
      loginForm:{
        loginId:null,
      }
    }
  }
  //ここまでを追加
}
</script>

<style scoped>
</style>
```

#### templete
データとフォームの入力項目をバインドするため、 `v-model` を使用しています。
`v-model`の詳細は、[公式ガイドのv-model](https://jp.vuejs.org/v2/api/#v-model)を参照ください。

#### script
 `data` は、アプリケーションで使用するデータを記述します。

- `data`の詳細は、[公式ガイドのdata](https://jp.vuejs.org/v2/api/#data)を参照

----

## フォームのバリデーションを設定
送信ボタンを押したタイミングでバリデーションをチェックする形のフォームバリデーションを追加します。
下記の2つをエラーが表示される条件として追加します。

- 値が入力されていない場合
- 半角英数以外で入力されている場合


``` HTML:src/components/LoginForm.vue
<template>
  <div>
    <p><b>{{title}}</b></p>
    <!-- v-on:submit.prevent="onSubmit" を追加 -->
    <form
    v-on:submit.prevent="onSubmit"
    >
      <span>ID : </span>          
      <input
        type="text"
        placeholder="input your id"
        v-model="loginForm.loginId"
        >
        <p>
          Input loginId is {{ loginForm.loginId }} 
        </p>
        <!-- ここから追加 -->
        <p class="error">
          {{ Validation.loginReult }}
        </p>
        <button v-on:click="checkFrom">
          送信する
        </button>
        <!-- ここまでを追加 -->
      </form>
  </div>
</template>

<script>
export default {
  props: {
      title : String
    },
  data: function() {
    return {
      loginForm:{
        loginId:null,
      },//ここにカンマを追加
      //ここから追加
      Validation:{
        loginReult: "",
      }
      //ここまでを追加
    }
  },//ここにカンマを追加
  //ここから追加
methods: {
    checkFrom: function(event){
      var LoginId = false
      
      //IDの入力フォームのバリデーション
      if (!this.loginForm.loginId) {
        this.Validation.loginReult="ログインIDを入力してください"
      } 
      else if(!this.checkString(this.loginForm.loginId)){
        this.Validation.loginReult="半角英数で入力してください"
      }else {
        LoginId = true
      }

      if(LoginId == true){
        this.Validation.loginReult=""
        alert('Hello,' + this.loginForm.loginId + '!');
      } 
      event.preventDefault() 
    },
    checkString: function(inputdata){
      var re = /^[A-Za-z0-9]*$/
      return re.test(inputdata);
    }
  }
  //ここまでを追加
}
</script>

<style scoped>
/* ここから追加 */
.error { color: red; }
/* ここまでを追加 */
</style>

```

#### template
`v-on:submit.prevent="onSubmit"` は、submit イベントによってページがリロードされないイベント修飾子です。

#### script
`<script>` 内の `methods` は、アプリケーションで使用するメソッドを指定します。
処理の分割やイベントハンドラなどを記述します。

----

ここまでの記述を追加し、フォームを空状態、全角文字で入力した状態、半角文字で入力した状態で送信ボタンをクリックして下記の挙動が再現されれば、完成です🎉

![form-validation-vue.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186755/bf44237a-fc08-7d70-e706-f65256960c49.gif)

----

# 追加演習1
`src/components/LoginForm.vue` にパスワードのフォームを追加し、フォームにバリデーションを設定する

### 条件
- フォームタイプは `password`
- 未入力時にはエラーを表示
- 8文字以上かつ英数字記号にもエラーを表示

### ヒント
英数字記号の正規表現は下記を使います。

``` HTML:正規表現
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!-/:-@[-`{-~])[!-~]$/i
```

### 回答サンプル
[GitHub：matsumana07384/form-validation-of-vue.js-sample](https://github.com/matsumana07384/form-validation-of-vue.js-sample)

# 追加演習2
- フォームの入力中にエラーを表示しよう

----

# その他のオプション
今回は使用していませんが、Vue.jsを理解する上で重要なオプションをご紹介します。

### computed
任意のデータを処理を返す算出プロパティ。

- 詳細は、[公式サイトの算出プロパティ](https://jp.vuejs.org/v2/guide/computed.html#%E7%AE%97%E5%87%BA%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3)を参照

### watch
特定のデータや算出プロパティの状態を監視して、データの変化をフックとして、処理を実行するプロパティ。

- 詳細は[公式サイトのガイド内のウォッチ](https://jp.vuejs.org/v2/guide/computed.html#%E3%82%A6%E3%82%A9%E3%83%83%E3%83%81%E3%83%A3)を参照。

----

# 最後に
頑張ってフォームのバリデーションを作りましたが、世の中には簡単にバリデーションを設定できる素晴らしいライブラリがあります。

- [vuelidate](https://github.com/vuelidate/vuelidate)
- [VeeValidate](https://github.com/logaretm/vee-validate)

これらを使えば、よりよいUXのフォームバリデーションをかんたんに開発できます。
よい世の中です。

----

他にもVue.jsにはたくさんの[サポートライブラリ](https://github.com/vuejs/awesome-vue#components--libraries)があるため、いろんなことができます。

今回のハンズオンではVue.jsの一部機能にしか触れておりません。
もっと知りたい！と思って頂けた方は、ぜひ[公式ガイド](https://jp.vuejs.org/v2/guide/)を読んだり、コミュニティに参加したりしてみてください！

ここまでご参加頂き、ありがとうございました🙏🏻