
はじめに
「ひな型あれば開発の導入が楽になりそう!」
というわけで、ログイン画面のひな型を作ることにしました。
入力フォームとバリデーション機能を実装しつつ、心ばかりですが拡張性も意識してみました。
解説はVeeValidate最低限の使い方がメインです。
細かいパラメータはドキュメントを参照しつつコードをいじっていただければ幸いです。
やること
- Vue2 × Vuetify × VeeValidate による入力バリデーション
- VeeValidate導入手順
- バリデーションルールの動的な変更
やらないこと
- Vueプロジェクト作成手順やVuetify導入手順
- サインイン処理
- 画面デザイン
- Vuexによる状態管理...etc
使った技術
-
Vue.js:投稿時点で最新は Vue3ですがVuetifyやVuex対応まで日和見してます
- @vue/composition-api:Vue3への移行を見据えて導入。
- Vuetify:マテリアルデザインに準拠したVue UIライブラリ。
-
VeeValidate:今回の主役。既存のバリデーションルールや任意のタイミングでバリデーションなど手軽さ&高カスタマイズ性を兼ね備えている。
VeeValidate
個人的なメリットはこんな感じです。
- 基本的なバリデーションルール、エラーは既存のものを使える!
- 独自のルールや任意のタイミングが指定できる!
- 他プロジェクトにも使いまわせる!
これまで自前で実装してましたがコードの見通しも悪く、チェックの抜け漏れを引き起こしがちでした。
ライブラリを使って品質UP&作業削減!
VeeValidateをプロジェクトに取り入れる
# install with npm
npm install vee-validate@next --save
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コンソールのサインイン画面が使いやすく好きなので寄せます。
- 最初はエラーメッセージを表示しない
- サインインが失敗した際にエラーメッセージでユーザーへフィードバック
実装するとこのような挙動になります。※条件に合わせてボタンの状態も切り替えています。
①入力値に適用するルールを決める
まずは、ユーザーネームとパスワードに適用するルールを決めます。
今回はmain.js内で取得した既存のバリデーションルールを用いて設定します。
validationRules: computed(() => {
// バリデーションルール
return {
base: {
required: true,
},
// 必須&emailアドレス形式
username: {
required: true,
email: true,
},
// 必須&8文字以上
password: {
required: true,
min: 8,
},
};
})
②入力フォームにルール適用
ルールを適用したい要素をバリデーション用コンポーネントで囲むのみです!
-
<ValidationObserver>
:invalidにバリデーションの結果が保持されています。 -
<ValidationProvider>
:rulesで適用したいルールを指定します。
細かいパラメータはドキュメントを参照ください。
<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]は必須です。
validate(state.inputPassword, state.validationRules.username, {
name: "ユーザーネーム",
}).then((result) => {
if (!result.valid) {
isInvalid = true;
}
resolve(isInvalid);
});
④ルールの動的な変更
条件を保持する変数を用意して<ValidationProvider>
のrulesに三項演算子で指定するだけでOK!
<ValidationProvider
v-slot="{ errors }"
name="ユーザーネーム"
:rules="
showErrorMessages
? validationRules.username
: validationRules.base
"
>
⑤がっちゃんこして出来上がり
細かいパラメータなどはドキュメントを参照ください。
表示するエラーメッセージの数やカスタムルールの設定が可能です。
<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
頑張ります