Reactのフォーム設計:制御vs非制御の「引き際」を極める
現場でコードをレビューしていると、フォームの実装で迷走している若手をよく見かけます。「とりあえず全部 `useState` で管理しておけば間違いないよね?」という思考停止に陥っていませんか?
結論から言おう。Reactにおけるフォーム設計は、「状態の同期コスト」と「ユーザー体験(UX)」のトレードオフをどこで妥協するかの戦いです。今日は、制御コンポーネントと非制御コンポーネントの境界線について、現場のリアルな視点から切り込んでいく。
—
1. 制御コンポーネント:Reactを「唯一の真実のソース」にする
制御コンポーネント(Controlled Components)は、Reactの `state` が入力値を握り、`value` を通じてDOMに値を押し付けるスタイルだ。
なぜこれを使うのか?
- 即時バリデーション: 入力するたびにパスワード強度を判定したり、ボタンの有効/無効を切り替えたりしたい場合、これ一択だ。
- 動的なUI変更: 入力値に応じて他のコンポーネントをリアルタイムで書き換えるなら、Reactが状態を把握していないと始まらない。
import { useState } from ‘react’;
export const ControlledForm = () => {
const [email, setEmail] = useState(”);
// ユーザーの入力のたびに再レンダリングが発生する
// 複雑なフォームだとパフォーマンス低下の懸念もあるが、制御は容易
return (
setEmail(e.target.value)}
placeholder=”メールアドレスを入力”
/>
);
};
現場の教訓: 「全項目を制御コンポーネントにする」のは大規模フォームでは自殺行為だ。タイピングのたびに親コンポーネント全体が再レンダリングされる。`React.memo` で防ぐという手もあるが、まずは「本当に全フィールドをリアルタイム同期させる必要があるか?」を自問してほしい。
—
2. 非制御コンポーネント:DOMに「任せる」という選択肢
非制御コンポーネント(Uncontrolled Components)は、`ref` を使ってDOMから直接値を取り出すスタイルだ。Reactは「値の管理」から手を引き、ブラウザ本来の挙動に任せる。
なぜこれを使うのか?
- パフォーマンス: 入力中の再レンダリングが一切発生しない。送信ボタンを押した瞬間に値が取れれば良いフォーム(ログイン画面や検索窓など)では、これが最強だ。
- 外部ライブラリとの親和性: DOMを直接操作するような古いライブラリを組み込む場合、非制御の方が圧倒的に相性が良い。
import { useRef } from ‘react’;
export const UncontrolledForm = () => {
// DOMへの参照を保持する
const inputRef = useRef
const handleSubmit = () => {
// 必要な時にだけ値を取りに行く(Pull型のアプローチ)
const value = inputRef.current?.value;
console.log(‘送信された値:’, value);
};
return (
<>
>
);
};
現場の教訓: 「Reactっぽくない」と敬遠するな。ブラウザネイティブのフォーム挙動を信じることは、コード量を減らし、意図しない再レンダリングを排除する最も堅実な最適化手法の一つだ。
—
3. プロの使い分け:現場での「選定基準」
中級エンジニアが陥りがちなのは、「どっちがいいか」という問いを立てることだ。正解は「要件に応じて使い分ける」ことに尽きる。
「制御」を選ぶべき場面
- リアルタイムバリデーション: 「パスワードが短すぎます」等の警告を即座に出す。
- 入力制限: 禁止文字の入力をその場でブロックする。
- 動的なフォーム: 選択肢に応じて次の入力欄がガラッと変わるような複雑なフォーム。
「非制御」を選ぶべき場面
- シンプルな送信フォーム: ログイン、アンケート、検索窓など、入力中はReactが状態を知る必要がないもの。
- パフォーマンス重視: 入力フィールドが100個を超えるような巨大なフォーム。
- ライブラリ活用: `react-hook-form` などのライブラリを導入する場合。実はこれ、内部的には「非制御」をベースに最適化されている。
—
最後に:理想的な「落とし所」
実務の現場では、React Hook Formのような優秀なライブラリを使うことが「標準」になりつつある。だが、ライブラリを使うにしても、「なぜ制御と非制御が存在するのか」という原理原則を知っているエンジニアと、そうでないエンジニアでは、トラブルシューティングの速度が天と地ほど違う。
まずは、小さなフォームから「これ、`useState` なしでも書けるんじゃないか?」と試してみてほしい。その「引き算の設計」こそが、堅牢で高速なアプリケーションを作る第一歩だ。
何か実装で詰まったら、またいつでも聞いてくれ。現場からは以上だ。

コメント