JavaScript | 1 日 120 分 × 7 日アプリ学習:フォームバリデーションアプリ

JavaScript
スポンサーリンク

4日目のゴールと今日のテーマ

4日目は「バリデーションの設計を“拡張しやすい形”に進化させる日」です。
昨日までで、あなたはすでに

  • 入力中の動的バリデーション
  • touched による UX 改善
  • 正規表現による形式チェック
  • submit 時の全体チェック

を体験しました。

今日はここからさらに一歩進めて、

  • バリデーションロジックを“再利用できる形”に整理する
  • フィールドが増えても壊れない設計にする
  • エラー表示を関数化して、render をスリムにする
  • UX をさらに自然にする(エラーの遅延表示など)

という「設計力の強化」にフォーカスします。


バリデーションロジックを「共通化」して拡張しやすくする

なぜ共通化が必要なのか

昨日までのコードは、メールとパスワードのバリデーションを
それぞれの input / blur イベントの中で直接呼んでいました。

しかし、フィールドが増えるとこうなります。

  • 名前
  • 電話番号
  • 郵便番号
  • パスワード確認
  • 生年月日

…イベントハンドラが増えるたびに、
「state 更新 → バリデーション → render」の流れを毎回書くことになります。

これは DRY(Don’t Repeat Yourself)違反 で、
コードが増えるほど壊れやすくなります。

共通バリデーション関数を作る

そこで、フィールド名を渡すと自動でバリデーションしてくれる関数を作ります。

function validateField(fieldName, value) {
  switch (fieldName) {
    case "email":
      return validateEmail(value);
    case "password":
      return validatePassword(value);
    default:
      return "";
  }
}
JavaScript

ここでの深掘りポイントは、

  • バリデーション関数を1カ所に集めることで、拡張が簡単になる
  • フィールド名をキーにして、バリデーションを切り替える設計

という「中級の設計パターン」を体験できることです。


入力イベントを「共通ハンドラ」で扱う

イベントハンドラを1つにまとめる

昨日まではこうでした。

emailInputEl.addEventListener("input", ...)
passwordInputEl.addEventListener("input", ...)
JavaScript

今日はこれを「共通化」します。

HTML 側に data-field 属性を付けます。

<input id="email" data-field="email" />
<input id="password" data-field="password" />

JavaScript で共通ハンドラを作ります。

function handleInput(event) {
  const field = event.target.dataset.field;
  const value = event.target.value;

  state[field] = value;

  if (state.touched[field]) {
    state.errors[field] = validateField(field, value);
  }

  render();
}
JavaScript

そしてイベント登録をまとめます。

emailInputEl.addEventListener("input", handleInput);
passwordInputEl.addEventListener("input", handleInput);
JavaScript

blur も共通化できる

function handleBlur(event) {
  const field = event.target.dataset.field;
  state.touched[field] = true;
  state.errors[field] = validateField(field, state[field]);
  render();
}

emailInputEl.addEventListener("blur", handleBlur);
passwordInputEl.addEventListener("blur", handleBlur);
JavaScript

ここでの重要ポイントは、

  • フィールドが増えても handleInput と handleBlur を使い回せる
  • バリデーションの追加は validateField に書くだけで済む

という「拡張性の高い設計」になっていることです。


エラー表示を関数化して render をスリムにする

render が太る問題

昨日までの render はこうでした。

emailErrorEl.textContent = state.errors.email;
passwordErrorEl.textContent = state.errors.password;
JavaScript

フィールドが増えると、render がどんどん太ります。

エラー表示を関数化する

function renderFieldError(fieldName, inputEl, errorEl) {
  const error = state.errors[fieldName];
  const touched = state.touched[fieldName];

  errorEl.textContent = touched ? error : "";

  if (error && touched) {
    inputEl.classList.add("input-error");
  } else {
    inputEl.classList.remove("input-error");
  }
}
JavaScript

render の中ではこう書けます。

function render() {
  renderFieldError("email", emailInputEl, emailErrorEl);
  renderFieldError("password", passwordInputEl, passwordErrorEl);

  submitButtonEl.disabled =
    Boolean(state.errors.email || state.errors.password) ||
    state.isSubmitting;

  formMessageEl.textContent = state.errors.form;
}
JavaScript

ここでの深掘りポイントは、

  • render の責務を「画面更新」に限定できる
  • エラー表示のロジックを1カ所に集められる
  • フィールド追加時の変更箇所が最小限になる

という「設計の整理」ができていることです。


UX をさらに自然にする:エラーの遅延表示

入力中にすぐ赤くなるとストレスになる

例えばパスワードを入力するとき、

  • 1文字目を入れた瞬間に「8文字以上にしてください」
  • 2文字目でまた「8文字以上にしてください」

…と毎回赤くなると、ユーザーは「うるさい」と感じます。

遅延バリデーション(debounce)を導入する

入力が止まってから 300ms 後にバリデーションする、という UX 改善ができます。

let debounceTimer = null;

function handleInput(event) {
  const field = event.target.dataset.field;
  const value = event.target.value;

  state[field] = value;

  clearTimeout(debounceTimer);

  debounceTimer = setTimeout(() => {
    if (state.touched[field]) {
      state.errors[field] = validateField(field, value);
      render();
    }
  }, 300);
}
JavaScript

ここでの重要ポイントは、

  • ユーザーが入力している最中はエラーを出さない
  • 入力が止まったタイミングでエラーを出す
  • UX が一気に自然になる

という点です。


submit 制御を「フォーム全体の状態」で判断する

フォーム全体のエラーをまとめて判定する

submit ボタンの disabled 判定を関数化します。

function hasFormError() {
  return Boolean(state.errors.email || state.errors.password);
}
JavaScript

render ではこう書けます。

submitButtonEl.disabled = hasFormError() || state.isSubmitting;
JavaScript

submit 時の流れを整理する

formEl.addEventListener("submit", (event) => {
  event.preventDefault();

  state.touched.email = true;
  state.touched.password = true;

  state.errors.email = validateEmail(state.email);
  state.errors.password = validatePassword(state.password);

  if (hasFormError()) {
    state.errors.form = "入力内容を確認してください。";
    render();
    return;
  }

  state.errors.form = "";
  state.isSubmitting = true;
  render();

  setTimeout(() => {
    state.isSubmitting = false;
    state.errors.form = "ログイン成功!(ダミー)";
    render();
  }, 800);
});
JavaScript

ここでの深掘りポイントは、

  • フォーム全体の状態を関数化して判断する
  • submit の流れが読みやすくなる
  • エラーがある場合の UX(フォーカス移動など)を追加しやすくなる

という「設計の整理」ができていることです。


4日目のまとめと、明日へのつなぎ

今日あなたが身につけたのは、まさに「設計力の中級ステップ」です。

  • バリデーションロジックを共通化して拡張しやすくした
  • 入力イベントと blur を共通ハンドラで扱えるようにした
  • エラー表示を関数化して render をスリムにした
  • 遅延バリデーションで UX を改善した
  • submit 制御を「フォーム全体の状態」で判断するようにした

明日(5日目)はここからさらに、

  • フィールド追加(パスワード確認・名前・電話番号など)を想定した設計
  • バリデーションルールの外部化(設定オブジェクト化)
  • エラー表示のテンプレート化(コンポーネント化)

など、「より大規模なフォームにも耐える設計」へ進みます。

今日のコードを触りながら、

  • フィールドを1つ増やすと何が必要か
  • どこを変えれば動くか
  • どこが変わらなくて済むか

を考えてみると、設計力が一気に伸びます。

タイトルとURLをコピーしました