この記事は、以前に投稿した「Vue & Vuetifyでバリデーション付きのフォームを作ってみる 」という記事の、Vue3.x 版となります。Vue2.xでの内容をご覧になりたい方は以前の記事をご覧ください。
VueとVuetifyを組み合わせて、下図キャプチャのようなバリデーション付きのフォームを作ってみます。Vueの基本事項は知っている前提です。だいぶ回りくどい記事になっています。
なお、本記事では、Options APIではなく、Composition APIを使用しています。
バージョン
- Vue 3.2.33
- Vuetify 3.0.0 Beta 3
Vuetifyは、Vue3.xに対応したVuetify3.xがまだ正式リリースされていません。(2022/6/3現在)
ですので、この記事ではベータ版を使用しています。今後、仕様が変更となる可能性がありますのでご注意ください。
STEP0: ベース
デモ
See the Pen Vue3 & Vuetify3でのバリデーション付きフォーム STEP:0 by テクニカル座 (@tekunikaruza) on CodePen.
解説
本記事では、以下のコードをベースに、バリデーション付きのフォームを作っていきます。
<div id="app">
<v-app>
<v-card>
<v-card-title>STEP:0 フォーム</v-card-title>
<v-card-text>
<v-text-field
v-model="state.text1"
label="入力必須で文字数制限のあるテキストフィールド"
>
</v-text-field>
<v-text-field
v-model="state.text2"
label="入力必須のみあるテキストフィールド"
>
</v-text-field>
<v-text-field
v-model="state.text3"
label="なんの制約もないテキストフィールド"
>
</v-text-field>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn text v-on:click="submit">送信する</v-btn>
<span v-if="state.success">送信成功!</span>
</v-card-actions>
</v-card>
</v-app>
</div>
const app = Vue.createApp({
setup() {
// リアクティブデータ
const state = Vue.reactive({
// 各テキストボックスの値
text1: '',
text2: '',
text3: '',
// 送信が成功したかどうかのフラグ
success: false,
})
// 送信を試みるメソッド(「送信する」がクリックされたときに呼ばれる)
const submit = () => {
state.success = true
}
return {
state,
submit,
}
},
})
app.use(Vuetify.createVuetify())
app.mount('#app')
Vuetifyを使用しているので、だいたいのコントロールはv-から始まる名前のタグになります。
入力必須や文字数制限のあるテキストボックスや、なんの制約もないテキストボックスも存在します。本来は、「送信する」をクリックすると、すべてのテキストボックスの入力検証をクリアしたときのみ、「送信成功!」と表示されてほしいのですが、今は、どんな状態でも「送信成功!」と表示されてしまします。
STEP1: 各テキストボックスに制約をつける
このSTEPでは、各テキストボックスに入力規則を設けます。これにより、各テキストボックスの値が規則にそぐわない場合は、エラー表示させることができます。
デモ
See the Pen Vue3 & Vuetify3でのバリデーション付きフォーム STEP:1 by テクニカル座 (@tekunikaruza) on CodePen.
解説
今回使用しているv-text-field
を含め、Vuetifyのすべてのフォームコントロールには、rules
という属性を付与でき、反映させたいバリデーション用関数の配列を渡すことで、1つ以上の制約をつけることができます。
rules
属性を付与する前に、あらかじめvue側で、バリデーション用関数を準備しておきます。ここでは、setup()
内に、必須入力と文字数制限の規則を反映させるための関数(requiredValidation()
とlimitLengthValidation()
)を用意しました。return時に、当該関数も含めています。
const app = Vue.createApp({
setup() {
// リアクティブデータ
const state = Vue.reactive({
// 各テキストボックスの値
text1: '',
text2: '',
text3: '',
// 送信が成功したかどうかのフラグ
success: false,
})
// ↓***** 新規追加
// バリデーション関数
const requiredValidation = (value) => !!value || '必ず入力してください' // 入力必須の制約
const limitLengthValidation = (value) => value.length <= 10 || '10文字以内で入力してください' // 文字数の制約
// ↑***** 新規追加
// 送信を試みるメソッド(「送信する」がクリックされたときに呼ばれる)
const submit = () => {
state.success = true
}
return {
state,
// ↓***** 新規追加
requiredValidation,
limitLengthValidation,
// ↑***** 新規追加
submit,
}
},
})
// ***** (後略) *****
それぞれの関数はどちらとも、引数として入力された文字列が渡され、制約に合致していればtrue
を返し、合致していなければエラーメッセージの文字列を返す関数です。
||
を使った書き方が見慣れない方は、下記ページが参考になります。
演算子 || の特殊な使用方法 [Java Script - 基本 - Tips]
基本的に入出力の形があっていれば、どんな関数の書き方でもいけるはず。。。
以上で、用意したバリデーション用関数を、各テキストボックスへ反映させます。最初に説明した、rules
属性を付与することで、必要な規則を1個でも2個でも何個でも反映させることができます。
<!-- (前略) -->
<!-- :rules属性を追加 -->
<v-text-field
v-model="state.text1"
label="入力必須で文字数制限のあるテキストフィールド"
:rules="[requiredValidation, limitLengthValidation]"
>
</v-text-field>
<!-- :rules属性を追加 -->
<v-text-field
v-model="state.text2"
label="入力必須のみあるテキストフィールド"
:rules="[requiredValidation]"
>
</v-text-field>
<!-- 特に変更なし -->
<v-text-field
v-model="state.text3"
label="なんの制約もないテキストフィールド"
>
</v-text-field>
<!-- (後略) -->
rules
属性の中で、さきほど作った関数を指定しています。(ですので、属性名の手前には、:
コロンまたは、v-bind:
をつける必要があります。詳しくはVueドキュメント参照。)
ご覧の通り、rules
属性が受け付けるのは、配列です。1番目のテキストボックスには、requiredValidation
と、limitLengthValidation
の2つの規則を付与しています。2番目は、requiredValidation
のみ。3番目は、特に規則が必要ないため、rules
属性も付与していません。
実際に、キーボードで入力してみると、バリデーションが動いていることがわかります。
Tips: 文字数カウンタをつける
このままでも良いのですが、1番目のテキストボックスは、「10文字まで」という制限があるにもかかわらず、ユーザは、入力を始めた時点では、最大入力可能文字数が何文字かがわかりません。さきほど実装したバリデーション用関数が発動してエラーメッセージが出て初めて、最大文字数の制約を知ることになります。これはちょっと不親切です。
これを解決する手段として、v-text-field
には、counter
という属性を付与することができます。これを使うと、その名の通りテキストボックスにカウンタが表示され、あと何文字入力できるか、ひと目でわかるようになります。
<!-- (前略) -->
<!-- counter属性を追加 -->
<v-text-field
v-model="state.text1"
label="入力必須で文字数制限のあるテキストフィールド"
:rules="[requiredValidation, limitLengthValidation]"
counter="10"
>
<!-- (後略) -->
注意点としては、rules
属性を使わずに、counter
属性だけを使ったとしても、入力文字数の制約自体はなく、実質何文字でも入力できる点です。文字数制限をかけたいときは、必ずrules
属性も併せて使ってください。
STEP2: フォーム全体をバリデーションチェック
STEP1が終わった時点で、各テキストボックスには入力規則をつけることができました。しかし、この時点でも、不正値が入力されていたとしても、「送信する」をクリックすると、送信成功となってしまいます。
このSTEPでは、これを解決します。
デモ
See the Pen Vue3 & Vuetify3でのバリデーション付きフォーム STEP:2 by テクニカル座 (@tekunikaruza) on CodePen.
解説
考え方としては、すべてのテキストボックスのバリデーションが通過しているかを確認し、通過している場合のみ、その先の処理を進める、という流れになります。
まず、現時点では、各テキストボックスが個々でバリデーションチェックをしているだけになりますので、これらをv-form
タグで囲って、1つのフォームであることを認識させます。このv-form
に対しては、後ほどVue側からアクセスしますので、ref
属性を使って名前をつけておきます。(ここではtestForm
)
<!-- (前略) -->
<v-card-text>
<!-- ↓ 新規追加 -->
<v-form ref="testForm">
<!-- ↑ 新規追加 -->
<v-text-field
v-model="state.text1"
label="入力必須で文字数制限のあるテキストフィールド"
:rules="[requiredValidation, limitLengthValidation]"
counter="10"
>
</v-text-field>
<v-text-field
v-model="state.text2"
label="入力必須のみあるテキストフィールド"
:rules="[requiredValidation]"
>
</v-text-field>
<v-text-field
v-model="state.text3"
label="なんの制約もないテキストフィールド"
>
</v-text-field>
<!-- ↓ 新規追加 -->
</v-form>
<!-- ↑ 新規追加 -->
</v-card-text>
<!-- (後略) -->
次に、Vue側では、「送信する」がクリックされたときに、v-form
内のすべてのコントロールのバリデーションが通過しているかを確認する処理を追記します。
const app = Vue.createApp({
setup() {
// リアクティブデータ
const state = Vue.reactive({
// ***** (中略) *****
// 送信が成功したかどうかのフラグ
success: false,
})
// ***** (中略) *****
// ↓***** 新規追加
// フォームコンポーネントの取得
const testForm = Vue.ref()
// ↑***** 新規追加
// ↓***** 修正
// 送信を試みるメソッド(「送信する」がクリックされたときに呼ばれる)
const submit = async () => {
// 検証
const validResult = await testForm.value.validate()
if (validResult.valid) {
state.success = true
} else {
state.success = false
}
}
// ↑***** 修正
return {
state,
requiredValidation,
limitLengthValidation,
// ↓***** 新規追加
testForm,
// ↑***** 新規追加
submit,
}
},
})
// ***** (後略) *****
まず、testForm
をsetup()
内で扱うために、const testForm = Vue.ref()
でコンポーネントを取得しています。同時に、setup()
のリターン内にtestForm
を加えています。(Vue3の仕様上、リターンしないと、testFormの中身がundefinedになってしまいます。)
続いて、「送信する」がクリックされたときに呼ばれるsubmit()
メソッドを修正します。
testForm.value.validate()
と書いて、testFormの中身のvalidate()
関数を呼び出します。この関数を呼ぶことで、v-form
で囲まれた全コントロールのバリデーションチェックをしてくれます。この関数は非同期関数ですので、返り値を取得するためにawait
をつけています。(なので、submit()
にはasync
をつけています。)
ここでは、返り値をvalidResult
に格納しています。
validResult
は、検証結果が格納されているオブジェクトになっており、validResult.valid
には、検証を通過したかどうかの真偽値が入っています。すべてのコントロールが検証を通過した場合はtrue
ですが、通過しなかったコントロールがあった場合はfalse
となり、同時に、該当コントロールをエラー表示にしてくれます。
ここでは、true
となった場合のみ、先の処理(ここでは「送信成功!」と表示させる)を進めさせています。
なお、validResult.errorMessages
には、該当するエラーメッセージの配列が格納されていますので、すべてのエラーメッセージを決まった場所に表示させたい場合などに利用できます。
なお、v-formには、validate()関数を含む3つの関数があります。
関数名 | 動作 |
---|---|
validate() |
バリデーションチェックを実施します。返り値の中身は上述の通り。 |
resetValidation() |
バリデーションチェックをリセットします。使いみちとしては、エラー表示を一旦すべて解除させたときなど。返り値はなし。 |
reset() |
すべての入力内容を初期値に戻します。また、バリデーションチェックもリセットされます。返り値はなし。 |
以上で、目標としていたフォームを作ることができました!
まとめ
rules
属性とv-form
を使えばOKということがわかりました。