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

JavaScript
スポンサーリンク

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

5日目は「フォームバリデーションを“複数フィールドに拡張できる設計”に進化させる日」です。
昨日までで、あなたはすでに

  • 入力中の動的バリデーション
  • touched による UX 改善
  • 正規表現による形式チェック
  • 共通ハンドラによるコード整理

を体験しました。

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

  • フィールド追加に強い設計(名前・パスワード確認など)
  • バリデーションルールの外部化(設定オブジェクト化)
  • エラー表示のテンプレート化(コンポーネント化)
  • submit 制御を“フォーム全体の状態”で判断する

という「中級フォームの本質」に踏み込みます。


フィールド追加に強い設計を作る

フィールドが増えると何が起きるか

例えば、次のフィールドを追加したいとします。

  • 名前(必須・2文字以上)
  • パスワード確認(password と一致するか)

昨日までのコードだと、次のような問題が起きます。

  • state にフィールドを追加
  • touched に追加
  • errors に追加
  • validateField に追加
  • render に追加
  • input / blur のイベント登録を追加

つまり、変更箇所が多すぎるのです。
これでは「拡張しやすい設計」とは言えません。

設計の方向性:フィールドを「設定」で管理する

フィールドごとに必要な情報を「設定オブジェクト」にまとめます。

const fields = {
  email: {
    selector: "#email",
    errorSelector: "#email-error",
    validate: validateEmail,
  },
  password: {
    selector: "#password",
    errorSelector: "#password-error",
    validate: validatePassword,
  },
};
JavaScript

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

  • フィールドの追加=fields に1行追加するだけ
  • DOM の取得も自動化できる
  • validateField の switch 文が不要になる

という「設定駆動の設計」ができることです。


設定オブジェクトから DOM と state を初期化する

DOM を自動で取得する

fields を使って、DOM をまとめて取得します。

for (const name in fields) {
  const config = fields[name];
  config.inputEl = document.querySelector(config.selector);
  config.errorEl = document.querySelector(config.errorSelector);
}
JavaScript

これで、fields.email.inputEl のようにアクセスできます。

state も自動で初期化する

const state = {
  values: {},
  errors: {},
  touched: {},
  isSubmitting: false,
};

for (const name in fields) {
  state.values[name] = "";
  state.errors[name] = "";
  state.touched[name] = false;
}
JavaScript

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

  • フィールドが増えても state の初期化が自動で行われる
  • state.values[name] で値を管理できる
  • state.errors[name] でエラーを管理できる

という「拡張性の高い状態管理」ができることです。


共通ハンドラを「設定ベース」で動かす

入力イベントの共通ハンドラ

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

  state.values[field] = value;

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

  render();
}
JavaScript

blur イベントの共通ハンドラ

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

  state.touched[field] = true;
  state.errors[field] = fields[field].validate(
    state.values[field],
    state.values
  );

  render();
}
JavaScript

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

  • validate 関数に state.values を渡すことで「他のフィールドとの比較」ができる
    例:パスワード確認(passwordConfirm === password)
  • フィールド追加時に handleInput / handleBlur を変更しなくてよい

という「柔軟なバリデーション設計」ができることです。


バリデーションルールを外部化する

validatePasswordConfirm の例

パスワード確認欄を追加したとします。

function validatePasswordConfirm(value, values) {
  if (!value) {
    return "確認用パスワードを入力してください。";
  }
  if (value !== values.password) {
    return "パスワードが一致しません。";
  }
  return "";
}
JavaScript

fields に追加するだけで動きます。

fields.passwordConfirm = {
  selector: "#password-confirm",
  errorSelector: "#password-confirm-error",
  validate: validatePasswordConfirm,
};
JavaScript

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

  • バリデーションルールが完全に独立している
  • フィールド追加時に既存コードを壊さない
  • validate 関数は純粋関数なのでテストしやすい

という「中級設計の理想形」に近づいていることです。


エラー表示をテンプレート化する

renderFieldError を設定ベースに書き換える

function renderFieldError(name) {
  const config = fields[name];
  const error = state.errors[name];
  const touched = state.touched[name];

  config.errorEl.textContent = touched ? error : "";

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

render 全体をスリムにする

function render() {
  for (const name in fields) {
    renderFieldError(name);
  }

  submitButtonEl.disabled = hasFormError() || state.isSubmitting;
  formMessageEl.textContent = state.errors.form;
}
JavaScript

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

  • render が「ループ1つ」で済むようになった
  • フィールド追加時に render を変更しなくてよい

という「設計の美しさ」です。


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

hasFormError を設定ベースで書く

function hasFormError() {
  return Object.values(state.errors).some((msg) => msg);
}
JavaScript

submit 時の流れ

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

  for (const name in fields) {
    state.touched[name] = true;
    state.errors[name] = fields[name].validate(
      state.values[name],
      state.values
    );
  }

  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 時に全フィールドを一括チェックできる
  • フィールド追加時に submit のコードを変更しなくてよい
  • フォーム全体の状態を一元管理できる

という「大規模フォームにも耐える設計」になっていることです。


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

今日あなたが身につけたのは、まさに「中級フォーム設計の核心」です。

  • フィールドを設定オブジェクトで管理する
  • state を values / errors / touched に分離して拡張性を高める
  • validate 関数を純粋関数として外部化する
  • render をテンプレート化してスリムにする
  • submit 制御を「フォーム全体の状態」で判断する

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

  • バリデーションルールの複合化(複数ルールの配列化)
  • エラーの優先順位を設定で管理する
  • フィールドごとの UI コンポーネント化
  • リアルタイム UX の最適化(遅延・即時・条件付き)

など、「実務レベルのフォーム設計」に近づけていきます。

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

  • フィールドを1つ追加したとき、どこを変えれば動くか
  • 逆に、どこを変えなくて済むか
  • 設計がどれだけ“壊れにくい”か

を確認してみると、設計力が一気に伸びます。

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