1
1

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 1 year has passed since last update.

Vue × Vuetify × VeeValidate で入力バリデーション付きログイン画面のひな型作成

Last updated at Posted at 2021-04-20
validation.gif

はじめに

「ひな型あれば開発の導入が楽になりそう!」
というわけで、ログイン画面のひな型を作ることにしました。

入力フォームとバリデーション機能を実装しつつ、心ばかりですが拡張性も意識してみました。

解説はVeeValidate最低限の使い方がメインです。
細かいパラメータはドキュメントを参照しつつコードをいじっていただければ幸いです。

やること

  • Vue2 × Vuetify × VeeValidate による入力バリデーション
  • VeeValidate導入手順
  • バリデーションルールの動的な変更

やらないこと

  • Vueプロジェクト作成手順やVuetify導入手順
  • サインイン処理
  • 画面デザイン
  • Vuexによる状態管理...etc

使った技術

  • Vue.js:投稿時点で最新は Vue3ですがVuetifyやVuex対応まで日和見してます:whale2:
  • @vue/composition-api:Vue3への移行を見据えて導入。
  • Vuetify:マテリアルデザインに準拠したVue UIライブラリ。
  • VeeValidate今回の主役。既存のバリデーションルールや任意のタイミングでバリデーションなど手軽さ&高カスタマイズ性を兼ね備えている。:whale2:

VeeValidate

個人的なメリットはこんな感じです。

  • 基本的なバリデーションルール、エラーは既存のものを使える!
  • 独自のルールや任意のタイミングが指定できる!
  • 他プロジェクトにも使いまわせる!

これまで自前で実装してましたがコードの見通しも悪く、チェックの抜け漏れを引き起こしがちでした。
ライブラリを使って品質UP&作業削減!

VeeValidateをプロジェクトに取り入れる

# install with npm
npm install vee-validate@next --save

main.tsに追記して有効化します。

main.ts
import Vue from 'vue';
import {
  configure,
  extend,
  localize,
  ValidationObserver,
  ValidationProvider,
} from 'vee-validate';
import ja from 'vee-validate/dist/locale/ja';

// 既存のバリデーションルールを取得
import { required, max, min, email } from 'vee-validate/dist/rules';

const config = {
  bails: false,
  mode: 'aggressive', //this is the default behavior and it validates whenever an input event is emitted.
};
configure(config);

extend('required', required);
extend('max', max);
extend('min', min);
extend('email', email);

// 日本語使用
localize('ja', ja);

// コンポーネント登録
Vue.component('ValidationObserver', ValidationObserver);
Vue.component('ValidationProvider', ValidationProvider);

画面作成

個人的にawsコンソールのサインイン画面が使いやすく好きなので寄せます。

  • 最初はエラーメッセージを表示しない
  • サインインが失敗した際にエラーメッセージでユーザーへフィードバック

実装するとこのような挙動になります。※条件に合わせてボタンの状態も切り替えています。
validation.gif

①入力値に適用するルールを決める

まずは、ユーザーネームとパスワードに適用するルールを決めます。
今回はmain.js内で取得した既存のバリデーションルールを用いて設定します。

SignIn.vue
validationRules: computed(() => {
  // バリデーションルール
  return {
    base: {
      required: true,
    },
    // 必須&emailアドレス形式
    username: {
      required: true,
      email: true,
    },
    // 必須&8文字以上
    password: {
      required: true,
      min: 8,
    },
  };
})

②入力フォームにルール適用

ルールを適用したい要素をバリデーション用コンポーネントで囲むのみです!

  • <ValidationObserver>:invalidにバリデーションの結果が保持されています。
  • <ValidationProvider>:rulesで適用したいルールを指定します。

細かいパラメータはドキュメントを参照ください。

SignIn.vue
<ValidationObserver v-slot="{ invalid }">
  <ValidationProvider
    v-slot="{ errors }"
    name="ユーザーネーム"
    rules="validationRules.username"
  >
    <v-text-field
      v-model="inputUsername"
      label="ユーザーネーム"
      :error-count="1"
      :error-messages="errors"
    />
  </ValidationProvider>
</ValidationObserver>

③任意のタイミングでバリデーション

validate()を用いるとコードからバリデーションできます。
第一引数にはバリデーション対象、第二引数にはルール、第三引数にはnameを指定します。
nameはエラーメッセージの主語にあたります。Ex.) [name]は必須です。

SignIn.vue
validate(state.inputPassword, state.validationRules.username, {
  name: "ユーザーネーム",
}).then((result) => {
  if (!result.valid) {
    isInvalid = true;
  }
  resolve(isInvalid);
});

④ルールの動的な変更

条件を保持する変数を用意して<ValidationProvider>のrulesに三項演算子で指定するだけでOK!

SignIn.vue
<ValidationProvider
  v-slot="{ errors }"
  name="ユーザーネーム"
  :rules="
    showErrorMessages
      ? validationRules.username
      : validationRules.base
  "
>

⑤がっちゃんこして出来上がり

細かいパラメータなどはドキュメントを参照ください。
表示するエラーメッセージの数やカスタムルールの設定が可能です。

SignIn.vue
<template>
  <v-container>
    <v-row justify="center">
      <v-col cols="6" align="center">
        <ValidationObserver v-slot="{ invalid }">
          <ValidationProvider
            v-slot="{ errors }"
            :name="vTEXT_FIELD_LABEL[0]"
            :rules="
              showErrorMessages
                ? validationRules.username
                : validationRules.base
            "
          >
            <v-text-field
              v-model="inputUsername"
              :label="vTEXT_FIELD_LABEL[0]"
              :error-count="1"
              :error-messages="showErrorMessages ? errors : null"
            />
          </ValidationProvider>
          <ValidationProvider
            v-slot="{ errors }"
            :name="vTEXT_FIELD_LABEL[1]"
            :rules="
              showErrorMessages
                ? validationRules.password
                : validationRules.base
            "
          >
            <v-text-field
              v-model="inputPassword"
              :type="showPassword ? 'text' : 'password'"
              :append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
              :label="vTEXT_FIELD_LABEL[1]"
              :error-count="1"
              :error-messages="showErrorMessages ? errors : null"
              @click:append="showPassword = !showPassword"
            />
          </ValidationProvider>
          <v-btn :disabled="invalid || isProcessing" @click="signIn()"
            >Go</v-btn
          >
        </ValidationObserver>
      </v-col>
    </v-row>
  </v-container>
</template>

<script lang="ts">
import {
  defineComponent,
  reactive,
  toRefs,
  computed,
} from '@vue/composition-api';
import { validate } from 'vee-validate';

const vTEXT_FIELD_LABEL = ['ユーザーネーム', 'パスワード'];

export default defineComponent({
  setup(props, context) {
    const state = reactive({
      inputUsername: '', // 入力されたユーザーネーム
      inputPassword: '', // 入力されたパスワード
      showPassword: false, // パスワードの表示制御
      showErrorMessages: false, // エラーメッセージの表示制御
      isProcessing: false, // サインイン処理の実行制御
      validationRules: computed(() => {
        // バリデーションルール
        return {
          base: {
            required: true,
          },
          username: {
            required: true,
            email: true,
          },
          password: {
            required: true,
            min: 8,
          },
        };
      }),
    });

    /**
     * ボタンによって呼び出されるサインイン処理
     */
    const signIn = async () => {
      // 状態を処理中にしてボタンを非活性化
      state.isProcessing = true;

      // 最初のエラーメッセージを表示していないときのみ実行
      if (!state.showErrorMessages) {
        // バリデーションルールに適しているかチェック
        if ((await isInvalidUsername()) || (await isInvalidPassword())) {
          // 適していなければ処理中断
          state.showErrorMessages = true;
          state.isProcessing = false;
          return;
        }
      }
      try {
        /* [TODO]サインイン処理 */
        context.root.$router.push(
          '/',
          () => {
            /* */
          },
          () => {
            /* */
          },
        );
      } catch (e) {
        // [TODO]ユーザーへのフィードバック
        state.isProcessing = false;
      }
    };

    /**
     * ユーザーネームのバリデーション
     * @return isInvalid 無効かどうか
     */
    const isInvalidUsername = () => {
      return new Promise((resolve) => {
        let isInvalid = false;
        validate(state.inputUsername, state.validationRules.username, {
          name: vTEXT_FIELD_LABEL[0],
        }).then((result) => {
          if (!result.valid) {
            isInvalid = true;
          }
          resolve(isInvalid);
        });
      });
    };

    /**
     * パスワードのバリデーション
     * @return isInvalid 無効かどうか
     */
    const isInvalidPassword = () => {
      return new Promise((resolve) => {
        let isInvalid = false;
        validate(state.inputPassword, state.validationRules.password, {
          name: vTEXT_FIELD_LABEL[1],
        }).then((result) => {
          if (!result.valid) {
            isInvalid = true;
          }
          resolve(isInvalid);
        });
      });
    };

    return {
      ...toRefs(state),
      vTEXT_FIELD_LABEL,
      signIn,
    };
  },
});
</script>

おわり

最低限の機能でひな型作成しました。
開発の始めにコピペしてパラメータいじるだけ!工数削減!!

今後も再利用性を意識してコンポーネント化、関数切り出し...etc
頑張ります:whale2:

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?