Vue3.0で使えるようになるComposition APIでのフォームバリデーションを実装します。一般的なユーザーの登録フォームをもとに見ていくので、割と網羅的な内容になっていると思います。
実装完了したコードはこちら。
必要なライブラリの導入
今回使うのはこれらです。
- @vue/cli@4.4.1
- vue2.6.11
- composition-api
- vue-composable
利用するVueは2.6.11です。Vue3.0でなくても、@composition-api
をインストールすればComposition APIが利用できます。
バリデーションに利用するライブラリは、vue-composable
です。VuelidateやVeeValidateはVue2.0系向けで、まだ3には対応していないようでした。
CLIインストール
まずは最新のCLIを入れます。古いものが入っている場合、削除してしまいましょう。
$ npm install @vue/cli@4.4.1 -g
プロジェクト作成・ライブラリ追加
Vueプロジェクトを作成し、Composition APIとvue-composable
を導入します。プロジェクト作成時のオプションはお好みで問題ないです。なお、今回はTypeScriptを利用しています。
$ vue create validate-sample $ cd validate-sample $ yarn add @vue/composition-api vue-composable
ここまでできたら一旦起動してみましょう。
$ yarn run serve
http://localhost:8080 で立ち上がります。このような画面が表示されればOKです。
CompositionAPIの設定
プラグインとしてComposition APIを利用することを設定します。src/main.ts
でimportとVue.use
をしましょう。
import Vue from 'vue' import App from './App.vue' import VueCompositionApi from "@vue/composition-api"; // 追記 Vue.config.productionTip = false Vue.use(VueCompositionApi); // 追記 new Vue({ render: h => h(App), }).$mount('#app')
補足: vue-composableについて
vue-composable
は、Composition APIで利用するためのコンポーネント群です。日時を扱うものや、文字列のフォーマット、ページネーション機能等も備えています。
非常に汎用性の高いものになっていますので、バリデーション以外の機能にも興味がある方はぜひご覧ください。
フォームの作成
バリデーションを試すために、まずは一般的なフォームを作成します。
項目は一般的なユーザーの登録フォームを想定しています。
- 氏名
- 年齢
- 生年月日
- プロフィール
- 電話番号
- 住所(省力化のため都道府県選択のみ)
以下のコードを /src/App.vue
にコピペすればOKです。
<template> <div id="app"> <form @submit.prevent="submit"> <div class="mt"> <label for="name-input">氏名</label> <input type="text" v-model="state.name" id="name-input" /> </div> <div class="mt"> <label for="age-input">年齢</label> <input type="number" v-model="state.age" id="age-input" /> </div> <div class="mt"> <label for="birth-input">生年月日</label> <input type="date" v-model="state.birthDay" id="birth-input" /> </div> <div class="mt"> <label for="profile-input">プロフィール</label> <textarea v-model="state.profile" rows="4" cols="40" id="profile-input" ></textarea> </div> <div class="mt"> <label for="tel-input">電話番号</label> <input type="text" v-model="state.telNumber" id="tel-input" /> </div> <div class="mt"> <label for="address-input">都道府県</label> <select v-model="state.address" id="address-input" > <option>都</option> <option>道</option> <option>府</option> <option>県</option> </select> </div> <div class="mt"> <input type="submit"/> </div> </form> </div> </template> <script lang="ts"> import {defineComponent, reactive} from '@vue/composition-api' type State = { name: string; age: number; birthDay: Date; profile: string; telNumber: string; address: string; } export default defineComponent({ setup() { const state = reactive<State>({ name: "", age: 20, birthDay: new Date(), profile: "", telNumber: "", address: "都" }); function submit() { alert('called submit') } return { state, submit } } }) </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } .mt { margin-top: 20px; } </style>
画面はこのような表示になります。不格好ですが、十分でしょう。
各入力欄の基本形はこちらです。ラベルとinput
のセットですね。これを項目の分だけ並べた形です。
<div class="mt"> <label for="name-input">氏名</label> <input type="text" v-model="state.name" id="name-input" /> </div>
では、バリデーションの実装をしていきましょう。
バリデーションの実装
公式ドキュメントはこちらです。
基本形の実装
vue-composable
のuseValidation
を利用し、バリデーションの定義をしていきます。nameの必須バリデーションを設定したscriptがこちら。
<script lang="ts"> import {defineComponent, reactive} from '@vue/composition-api' import {useValidation} from 'vue-composable' // 追加 type State = { name: string; age: number; birthDay: Date; profile: string; telNumber: string; address: string; } const required = x => !!x // 追加 export default defineComponent({ setup() { const state = reactive<State>({ name: "", age: 20, birthDay: new Date(), profile: "", telNumber: "", address: "都" }); // 追加 const form = useValidation({ name: { $value: state.name, required } }) function submit() { alert('called submit') } return { state, form, // 追加 submit } } }) </script>
useValidation
で、バリデーションの設定を生成しています。構文は下記の通りです。
useValidation({ <プロパティ名>: { $value: <監視する対象の値>, <バリデータ> } })
監視する対象にはリアクティブな値を入れましょう。reactiveで生成しても、refで生成してもOKです。バリデータは、$value
とcontext
を受け取る関数を指定します。先ほどの例では、x => !!x
を指定しました。受け取った値を真偽値に変換しています。
一点補足で、現時点では公式トップにCurrently there's no exported validators.
との文言が表示されています。将来的に標準のバリデータが提供されるということでしょうか。現時点ではライブラリからバリデータが提供されていないので、今回はrequired
バリデータを自前実装しています。
さて、これが正常に動作しているか確認してみましょう。先程のフォームの氏名の部分をこのコードに置き換えます。
<div class="mt"> <label for="name-input">氏名</label> <input type="text" v-model="form.name.$value" id="name-input" /> <p>any invalid?: {{form.name.$anyInvalid}}</p> </div>
form.name.$value
でリアクティブな値にアクセスできるようになっています。これをv-model
にセットしました。pタグの中に書いてあるform.name.$anyInvalid
は、そのプロパティが有効か無効かを返します。氏名が空の場合はrequired
がfalseを返すため、$anyInvalid
はtrueになります。実際にフォームを触って確かめてみてください。
エラー時メッセージの設定
不正な値の場合、その旨をユーザーに伝える必要があります。先程のバリデーションに、エラーメッセージを追加しましょう。
export default defineComponent({ setup() { const state = reactive<State>({ name: "", age: 20, birthDay: new Date(), profile: "", telNumber: "", address: "都" }); const form = useValidation({ name: { $value: state.name, required, $message: "必須項目です" },
$message
を追加します。この値はform.name.$message
でアクセスすることが可能です。
下記のように氏名の部分を書き換えると、不正な値があった場合メッセージが表示されるようになります。一番下にpタグを追加した形ですね。
<div class="mt"> <label for="name-input">氏名</label> <input type="text" v-model="form.name.$value" id="name-input" /> <p v-if="form.name.$anyInvalid">{{form.name.$message}}</p> </div>
数値チェックの実装
先ほどは氏名の必須チェックを実装しましたが、次は年齢の数値チェックをします。
const required = x => !!x // 追加 const numerically = v => { return v.match(/\d+/) }; export default defineComponent({ setup() { const state = reactive<State>({ name: "", age: 20, birthDay: new Date(), profile: "", telNumber: "", address: "都" }); const form = useValidation({ name: { $value: state.name, required, $message: "必須項目です" }, // 追加 age: { $value: state.age, numerically, $message: "数値を入力してください" },
validatorに、新たに定義したnumerically
を設定します。templateのv-model
もform.age.$value
を利用するよう更新しておきましょう。
これでname同様バリデーションが効くようになっているはずです。最後に電話番号のバリデーションを実装しましょう。
フォーマットチェックの実装
useValidation
に、telNumberについてのバリデーションを追加します。
const required = x => !!x const numerically = v => { return v.match(/\d+/) }; // 追加 const telNumberFormat = v => { return v.match(/\d{2,3}-\d{1,4}-\d{4}$/) } export default defineComponent({ setup() { const state = reactive<State>({ name: "", age: 20, birthDay: new Date(), profile: "", telNumber: "", address: "都" }); const form = useValidation({ name: { $value: state.name, required, $message: "必須項目です" }, age: { $value: state.age, numerically, $message: "数値を入力してください" }, // 追加 telNumber: { $value: state.telNumber, required: { $validator: required, $message: "必須項目です" }, format: { $validator: telNumberFormat, $message: "電話番号の形式が不正です" } }
ネストさせ、複数のバリデーションを行うことができます。電話番号では必須チェックとフォーマットチェックを行うようにしました。それぞれ、form.telNumber.required.プロパティ名
/ form.telNumber.format.プロパティ名
でアクセスできます。
完成形
最後にsubmitボタンの活性制御を追加すれば完成です。
<template> <div id="app"> <form @submit.prevent="submit"> <div class="mt"> <label for="name-input">氏名</label> <input type="text" v-model="form.name.$value" id="name-input" /> </div> <div class="mt"> <label for="age-input">年齢</label> <input type="number" v-model="form.age.$value" id="age-input" /> </div> <div class="mt"> <label for="birth-input">生年月日</label> <input type="date" v-model="state.birthDay" id="birth-input" /> </div> <div class="mt"> <label for="profile-input">プロフィール</label> <textarea v-model="form.profile.$value" rows="4" cols="40" id="profile-input" ></textarea> </div> <div class="mt"> <label for="tel-input">電話番号(ハイフン含)</label> <input type="text" v-model="form.telNumber.$value" id="tel-input" /> </div> <div class="mt"> <label for="address-input">都道府県</label> <select v-model="state.address" id="address-input" > <option>都</option> <option>道</option> <option>府</option> <option>県</option> </select> </div> <div class="mt"> <input :disabled="form.$anyInvalid" type="submit" /> </div> <div> <p v-if="form.name.$anyInvalid">氏名: {{form.name.$message}}</p> <p v-if="form.age.$anyInvalid">年齢: {{form.age.$message}}</p> <p v-if="form.birthday.$anyInvalid">生年月日: {{form.birthday.$message}}</p> <p v-if="form.profile.$anyInvalid">プロフィール: {{form.profile.$message}}</p> <p v-if="form.telNumber.$anyInvalid">電話番号: {{form.telNumber.required.$message}} {{form.telNumber.format.$message}}</p> </div> </form> </div> </template> <script lang="ts"> import {defineComponent, reactive} from '@vue/composition-api' import {useValidation} from 'vue-composable' type State = { name: string; age: number; birthDay: Date; profile: string; telNumber: string; address: string; } const required = x => !!x const numerically = v => { return v.match(/\d+/) }; const telNumberFormat = v => { return v.match(/\d{2,3}-\d{1,4}-\d{4}$/) } export default defineComponent({ setup() { const state = reactive<State>({ name: "", age: 20, birthDay: new Date(), profile: "", telNumber: "", address: "都" }); const form = useValidation({ name: { $value: state.name, required, $message: "必須項目です" }, age: { $value: state.age, numerically, $message: "数値を入力してください" }, birthday: { $value: state.birthDay, required, $message: "必須項目です" }, profile: { $value: state.profile, required, $message: "必須項目です" }, telNumber: { $value: state.telNumber, required: { $validator: required, $message: "必須項目です" }, format: { $validator: telNumberFormat, $message: "電話番号の形式が不正です" } } }) function submit() { alert('called submit') } return { state, form, submit } } }) </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } .mt { margin-top: 20px; } </style>
このコードは多少雑な作りになっていますが、うまくアレンジして利用してみてください。
まとめ
基本形からちょっとした応用まで紹介したので、これをベースに拡張していけば大抵の要件には対応できるかと思います。非常に柔軟性が高く使い勝手の良い機能ですし、そこまで癖のない馴染みある構文ですね。一部触れなかった構文もありますので、興味のある方はぜひ公式ドキュメントも読んでみてください。 https://pikax.me/vue-composable/composable/validation/validation.html
以上、vue-composable
を利用したバリデーション実装でした。