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();
}
JavaScriptblur イベントの共通ハンドラ
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 "";
}
JavaScriptfields に追加するだけで動きます。
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");
}
}
JavaScriptrender 全体をスリムにする
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);
}
JavaScriptsubmit 時の流れ
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つ追加したとき、どこを変えれば動くか
- 逆に、どこを変えなくて済むか
- 設計がどれだけ“壊れにくい”か
を確認してみると、設計力が一気に伸びます。


