入力を検証する
このレッスンで分かること
^と$で文字列の全体を縛る書き方- メールアドレスを「実務で使う程度」に検証するパターンの組み立て方
gフラグ付きの正規表現をtestで使い回すと 2 回目が落ちる理由
入力を検証する とは
フォームに入った文字が期待した形かどうかを、正規表現で判定することです。全体を縛る
^と$が要になります。
一部に当たるか、全体が当たるか
前のレッスンの /\d{4}/ は「4 個並んだ数字がどこかにある」という意味でした。検証ではこれでは足りません。郵便番号の入力欄に あ1234い と入れられても true になってしまうからです。
文字列の先頭と末尾を指定する記号が用意されています。
| 記号 | 意味 |
|---|---|
^ | 文字列の先頭 |
$ | 文字列の末尾 |
両方で挟むと「全体がこの形であること」になります。
console.log(/\d{4}/.test("あ1234い")); // true
console.log(/^\d{4}$/.test("あ1234い")); // false
console.log(/^\d{4}$/.test("1234")); // true検証で使う正規表現は、ほぼ必ず ^ で始まり $ で終わります。逆に言うと、^ と $ が無い検証用パターンを見かけたら、まず疑ってよい場所です。
^は角かっこの中では「以外」を意味しました。[^abc]と^abcは別物です。角かっこの外に出てきた^は先頭を表す、と覚えると混ざりません。
空白だけの入力をはじく
入門のフォームでは name.length === 0 で空を弾きました。ところがこれは、半角スペースだけを入れられると通ってしまいます。
const name = " ";
console.log(name.length === 0); // false 通ってしまう「全体が空白文字 0 個以上」という形にすれば、空文字列とスペースだけの両方を 1 本で弾けます。
const blank = /^\s*$/;
console.log(blank.test("")); // true
console.log(blank.test(" ")); // true
console.log(blank.test("山田 太郎")); // false\s は空白文字 1 個、* は 0 個以上なので、^\s*$ は「先頭から末尾まで空白文字しか無い」を表します。空文字列も 0 個として当たるところがうまい点です。
メールアドレスをどこまで見るか
ここで正直に書いておきます。メールアドレスの正しさを正規表現だけで判定することはできません。
理由は 2 つあります。1 つは、メールアドレスの仕様が想像よりはるかに複雑で、引用符やコメントまで許しているため、仕様どおりの正規表現は数百文字になり、書いても誰も読めないことです。もう 1 つは、形が合っていてもそのアドレスが実在するとは限らないことです。taro@example.com は形として完璧ですが、届くかどうかは送ってみないと分かりません。
実務では、次のように割り切ります。
| 段階 | 何をするか |
|---|---|
| 入力時 | 明らかな打ち間違いを弾く。@ が無い、ドットが無い、空白が入っている |
| 送信後 | 確認メールを送り、リンクを踏んでもらって初めて有効とみなす |
正規表現が担当するのは 1 段目だけです。そのつもりで、短いパターンを書きます。
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
console.log(emailPattern.test("taro@example.com")); // true
console.log(emailPattern.test("taro@example")); // false
console.log(emailPattern.test("taroexample.com")); // false
console.log(emailPattern.test("ta ro@example.com")); // false読み方は次のとおりです。[^\s@]+ は「空白でも @ でもない文字が 1 個以上」です。それが @ を挟んで前後にあり、後ろ側にはさらに \. で区切られた部分がある、という形です。
つまり「アットマークがちょうど 1 個あって、その後ろにドットがある」だけを見ています。これで打ち間違いの大半は捕まえられますし、正しいアドレスを誤って弾く事故は起きません。
検証のパターンは、厳しくするほど正しい入力を弾く危険が上がります。弾かれた利用者は原因が分からないので、そのまま離脱します。迷ったらゆるいほうに倒すのが定石です。
よく使う検証パターン
手を動かす前に、形が決まっているものをいくつか見ておきます。どれも ^ と $ で挟んである点は同じです。
| 見たいもの | パターン | 当たる例 |
|---|---|---|
| 郵便番号 | /^\d{3}-\d{4}$/ | 150-0001 |
| 半角英数字だけ | /^[A-Za-z0-9]+$/ | taro2026 |
| 全体が空白だけ | /^\s*$/ | |
| 4 桁の西暦 | /^\d{4}$/ | 2026 |
console.log(/^\d{3}-\d{4}$/.test("150-0001")); // true
console.log(/^\d{3}-\d{4}$/.test("1500001")); // falseハイフンの有無まで縛るかどうかは、入力欄の作り方しだいです。ハイフン無しでも受けたいなら /^\d{3}-?\d{4}$/ のように ? を足します。? は 0 個か 1 個という意味なので、これだけで両方を通せます。
日本語の入力欄では、利用者が全角数字を打つことがあります。
\dは半角数字しか見ないので、150-0001は弾かれます。弾いてメッセージを出すのか、こちらで半角に直してから見るのかは、検証を書く前に決めておく話です。
エラーメッセージをまとめて返す
課題では、入力オブジェクトを受け取ってエラーメッセージの配列を返す関数を書きます。配列にするのは、複数の問題を一度に伝えるためです。1 つ直すたびに次のエラーが出てくる画面は、直しているほうが疲れます。
const errors = validateContact({ name: "", email: "taro" });
console.log(errors);
// ["お名前を入力してください", "メールアドレスの形式が正しくありません"]問題が無ければ空配列を返します。呼ぶ側は errors.length === 0 で成否を判定できます。
よくある間違い
g フラグを付けた正規表現を test で使い回す
これは知らないと必ず一度は踏みます。g フラグ付きの正規表現オブジェクトは lastIndex という「次にどこから探すか」を内部に持っていて、test が当たるたびにそれを進めます。関数の外に置いて使い回すと、2 回目の呼び出しが前回の続きから探し始めて false になります。
// NG 関数の外に g 付きで置いた
const pattern = /\d{4}/g;
function hasYear(text) {
return pattern.test(text);
}
console.log(hasYear("2026年")); // true
console.log(hasYear("2026年")); // false 同じ引数なのに結果が変わる対処は 2 つあります。検証に g は不要なので付けないのが一番です。どうしても付けるなら、関数の中で毎回作り直します。
// OK 検証に g は要らない
const pattern = /\d{4}/;
function hasYear(text) {
return pattern.test(text);
}^ と $ を付け忘れる
付け忘れると、途中に混ざっていれば通る判定になります。
console.log(/[^\s@]+@[^\s@]+\.[^\s@]+/.test("問い合わせ先 taro@example.com まで")); // true
console.log(/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test("問い合わせ先 taro@example.com まで")); // false空文字列を弾けていると思い込む
+ は 1 個以上なので空文字列は弾けますが、* は 0 個以上なので通します。空の入力を弾くつもりで * を書くと素通りします。
console.log(/^\w*$/.test("")); // true 弾けていない
console.log(/^\w+$/.test("")); // false こちらが正しい要件
- 関数名は validateContact、引数は { name, email } の形のオブジェクトを 1 つ受け取る
- name が空文字列または空白文字だけなら「お名前を入力してください」を入れる
- email が /^[^\s@]+@[^\s@]+.[^\s@]+$/ に当たらなければ「メールアドレスの形式が正しくありません」を入れる
- 戻り値は文字列の配列。両方に問題があれば name のメッセージを先に、問題が無ければ空配列を返す
入出力例
validateContact({"email":"taro@example.com","name":"山田 太郎"}) → []
validateContact({"email":"taro@example.com","name":""}) → ["お名前を入力してください"]
validateContact({"email":"taro@example.com","name":" "}) → ["お名前を入力してください"]
validateContact({"email":"taro@example","name":"山田 太郎"}) → ["メールアドレスの形式が正しくありません"]
validateContact({"email":"taroexample.com","name":""}) → ["お名前を入力してください","メールアドレスの形式が正しくありません"]