対象読者
- JavaScriptとWeb開発の基礎に理解がある方
- Reactを用いたJavaScriptアプリケーション開発の経験者
前提環境
筆者の検証環境は以下の通りです。
- macOS Big Sur 11.1
- Node.js 15.5.0/npm 7.3.0
- React 17.0.1
- react-scripts 4.0.1
- Formik 2.2.6
- Yup 0.32.8
バリデーションを宣言的に記述する
今回はフォームのバリデーションに役立つライブラリ、Yupについて解説します。前回解説した、フォーム作成補助ライブラリのFormikでは、入力内容のバリデーションをリスト1のように記述していました。
<Formik
validate={values => {
const errors = {};
if (!values.email) {
errors.email = 'Eメールを入力してください'
}
return errors;
}}
>
validate属性の関数に、すべての入力欄の値が入ったオブジェクトであるvaluesが渡されるので、それの正否を判定し、もし誤りがあればvaluesオブジェクト内の項目名と同名のキーでerrorsオブジェクトにエラーメッセージを登録することになっていました。データに対してif文を駆使しながら1行ずつ処理を書いて判定してするので、いわゆる手続き的プログラミングの手法でバリデーションルールを実装することになります。
UIの操作はReactのおかげで宣言的に記述できるようになっています。可能であれば、バリデーションルールの定義も宣言的プログラミングで行いたいですよね。そんな夢を叶えてくれるのが、Yupというバリデーション専門のライブラリです。Yupが生成するバリデーションルールはFormikでも公式サポートされているので、前回の内容と組み合わせることで、より快適にフォームを組み上げられることでしょう。
Yup
YupはJason Quense氏が開発している、バリデーションのためのライブラリです。
GitHubのREADME(説明書)には、Yupは"JavaScript schema builder for value parsing and validation"である、と書かれています。フォームの入力値を解析してバリデーションを行うために、JavaScriptでスキーマ(データ構造)を定義するためのライブラリである、ということです。ただ、スキーマと言われてもピンとこないと思うので、実際の例を見てみましょう。スキーマはリスト2のように、関数のチェーン呼び出しを組み合わせながら定義します。
import * as yup from 'yup';
const schema = yup.object().shape({ // (1)
name: yup.string().required(), // (2)
age: yup.number().required().positive().integer(), // (3)
email: yup.string().email(), // (4)
});
(1)のyup.object()は、判定対象の入力値がオブジェクトで提供されることを期待する定義です。続いて、実際のデータ構造を.shape()で定義します。(2)はnameという項目が文字列型の必須項目であることを示しています。(3)はageという項目が数値型の必須項目であり、正の整数であることを期待する定義です。(4)はemailという項目が文字列型であり、メールアドレスとして正しい形式であることを期待する定義です。emailにはrequired()が付いていないので、任意入力の項目です。
Yupのスキーマ定義は、このように各項目の特性を並べるだけで作成できます。バリデーションルールを宣言的に記述できる、魅力的なインターフェースですね。
さて、本来はフォームに組み込んで使うライブラリですが、単独で動作させるためのインターフェースも用意されています。リスト2のスキーマを使って値を検証する場合は、リスト3のような書き方になります。
schema
.isValid({ // (1)
name: 'Taro',
age: 24,
})
.then(function (valid) { // (2)
// ...
});
Yupのスキーマオブジェクト(リスト2で作成したschema)には、データを検証するための関数として(1)のisValidが用意されています。スキーマの最上位のデータ型としてobject()を定義していたので、isValidに渡すデータもオブジェクトです。バリデーションは非同期で行われ、Promiseで返却されます。(2)のように.then()にコールバック関数を登録することで、入力値の正否をboolean型で受け取れるので、これを受けて次の処理を実施することになります。これがYupの最もシンプルな使い方です。
任意のエラーメッセージを扱う
さて、ここまでの解説では、エラーメッセージを定義する場所も受け取る場所もありませんでした。エラーメッセージを受け取りたい場合は、isValid()の代わりにvalidate()を使います(リスト4)。
schema
.validate({
name: 'Taro',
age: 'hi', // 数値型ではなく文字列型を渡している
})
.then(function (value) { // (1)
// ...
})
.catch(function (err) { // (2)
console.log(err.errors); // => ['age must be a number']
});
validate()を使った場合は、isValidのときと比べて挙動が変わります。成功した場合である(1)の.then()には、検証対象だった値(validate()の第一引数)がそのまま渡されます。一方、失敗した場合である(2)の.catch()には、エラーメッセージの配列が渡されます。ようやくここでエラーメッセージを見ることができました。
エラーメッセージが英語で記載されているのが気になりますが、これはカスタマイズできます。スキーマを定義する時点で、各条件の関数に引数として文字列を渡すと、バリデーションエラーが起きたときにエラーメッセージとして利用されるのです(リスト4)。
import * as yup from 'yup';
const schema = yup.object().shape({
name: yup.string().required('名前は必須項目です'),
age: yup.number('年齢は数値で入力してください') // (1)
.required('年齢は必須項目です')
.positive('正の数を指定してください')
.integer('整数で指定してください'),
email: yup.string().email('メールアドレスの形式が不正です'),
});
schema
.validate({
name: 'Taro',
age: 'hi', // 数値型ではなく文字列型を渡している
})
.catch(function (err) {
console.log(err.errors); // => ['年齢は数値で入力してください'] // (2)
});
リスト2のスキーマ定義に少し手を加えて、各条件にメッセージを登録しました。(1)でageが数値ではなかった場合のメッセージを登録しています。これはリスト3の例と同様にエラーになり、(2)には(1)と同じ文字列がエラーメッセージとして渡されてきます。このように、日本語のエラーメッセージを扱うことも可能です。
エラーメッセージを登録し忘れると、画面に英語のメッセージが出てくることになるので、業務で利用する場合には注意が必要かもしれません。
