Reactフォーム管理における永遠の議論
Wenhao Wang
Dev Intern · Leapcell

はじめに
フロントエンド開発の活気に満ちた絶え間なく進化する世界において、ユーザー入力、特にフォームを通じた入力の処理は、ほとんどのインタラクティブアプリケーションの基盤となります。Reactは、その強力なコンポーネントベースのアーキテクチャにより、フォームデータを管理するためのいくつかのパラダイムを提供しており、それぞれに独自の利点と理想的なユースケースがあります。React開発者の間で最も頻繁に議論され、時には賛否両論を呼ぶトピックの1つは、useStateを使用して制御コンポーネントでフォーム入力を管理するか、useRefを使用して非制御コンポーネントで管理するか、という選択です。これは単なる好みの問題ではありません。コンポーネントの再レンダリング、データフロー、検証戦略、そしてアプリケーション全体のパフォーマンスと保守性に影響を与える決定です。これらの2つのアプローチのニュアンスを理解することは、堅牢で効率的なユーザーインターフェースを構築するために不可欠です。この記事では、この議論の中心に迫り、各アプローチの定義、実装例、そしてそれぞれの長所と短所を分析します。
本文
フォームでのuseStateとuseRefの具体例に入る前に、コアコンセプトである制御コンポーネントと非制御コンポーネントを明確に理解しておきましょう。
制御コンポーネント: 制御コンポーネントとは、Reactがフォームデータを直接管理するコンポーネントのことです。入力要素の値は、Reactコンポーネント内の状態の一部によって制御されます。入力へのすべての変更は、状態を更新するイベントハンドラによって処理され、その状態がコンポーネントを再レンダリングし、入力で表示される値を更新します。これにより、入力データの一元的な真実の源が生まれます。
非制御コンポーネント: 対照的に、非制御コンポーネントとは、フォームデータがDOM自体によって処理されるコンポーネントのことです。すべての状態更新に対してイベントハンドラを記述する代わりに、DOMにデータを管理させ、refを使用して、必要に応じて(例:フォームが送信されるとき)フォームフィールドから値を取得します。
それでは、useStateがどのように制御コンポーネントをサポートし、useRefが非制御コンポーネントをどのように強化するかを見てみましょう。
useStateによる制御コンポーネント
原則: 制御コンポーネントでは、入力要素の値は常にReactの状態によって駆動されます。ユーザーが入力すると、onChangeハンドラが状態を更新し、入力は新しい状態値で再レンダリングされます。
実装例:
import React, { useState } from 'react'; function ControlledForm() { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const handleSubmit = (event) => { event.preventDefault(); // デフォルトのフォーム送信を防止 console.log('Submitted Name:', name); console.log('Submitted Email:', email); // APIなどにこのデータを送信できます。 }; return ( <form onSubmit={handleSubmit}> <label> Name: <input type="text" value={name} onChange={(e) => setName(e.target.value)} /> </label> <br /> <label> Email: <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> </label> <br /> <button type="submit">Submit</button> </form> ); } export default ControlledForm;
長所:
- 一元的な真実の源: Reactの状態は入力値の絶対的なソースであり、予測可能でデバッグしやすくなります。
 - 即時検証とフィードバック: ユーザーが入力すると、リアルタイム検証ロジックを実装し、即時フィードバックを提供できます。
 - 簡単な操作: 入力値をプログラムで簡単に操作できます(例:フォームのリセット、値の事前入力、入力のフォーマット)。
 - 単純な条件付きロジック: 入力値に基づいてボタンを無効にしたり、要素を表示/非表示にしたりすることが簡単になります。
 - 動的フォームに最適: 他の入力値に基づいて表示フィールドが必要になる場合。
 
短所:
- パフォーマンスのオーバーヘッド: キーストロークごとに
onChangeイベントが発生し、状態の更新とコンポーネント(およびその子)の再レンダリングにつながります。多くの入力があるフォームや高頻度の更新では、これが懸念される場合があります。 - より多くのボイラープレート: 各入力フィールドの
onChangeハンドラを記述し、状態を管理する必要があります。 
useRefによる非制御コンポーネント
原則: 非制御コンポーネントでは、Reactは入力値を管理しません。代わりに、useRefを使用してDOM入力要素への直接参照を取得し、必要に応じて(通常はフォーム送信時)その値を取得します。
実装例:
import React, { useRef } from 'react'; function UncontrolledForm() { const nameInputRef = useRef(null); const emailInputRef = useRef(null); const handleSubmit = (event) => { event.preventDefault(); // デフォルトのフォーム送信を防止 console.log('Submitted Name:', nameInputRef.current.value); console.log('Submitted Email:', emailInputRef.current.value); // APIなどにこのデータを送信できます。 }; return ( <form onSubmit={handleSubmit}> <label> Name: <input type="text" ref={nameInputRef} /> </label> <br /> <label> Email: <input type="email" ref={emailInputRef} /> </label> <br /> <button type="submit">Submit</button> </form> ); } export default UncontrolledForm;
長所:
- より少ないボイラープレート: 特にシンプルなフォームでは、
onChangeハンドラや各入力の状態は不要です。 - 潜在的に優れたパフォーマンス: Reactが入力値を直接管理していないため、キーストロークごとの再レンダリングはありません。これは、中間再レンダリングがコストのかかる非常に大きなフォームや高性能なシナリオに有益な場合があります。
 - 非Reactコードとの容易な統合: サードパーティのDOMライブラリと統合する必要がある場合、直接DOM操作を期待します。
 
短所:
- Reactによる制御の喪失: Reactは入力の現在の値を即座に認識しません。
 - 手動検証: リアルタイム検証は、現在の値が状態で容易に利用できないため、より困難です。通常は送信時のみ検証します。
 - 簡単なプログラムによる更新なし: 入力値をプログラムで変更することは、それほど慣用的ではありません。
ref.current.valueにアクセスして直接設定する必要があり、Reactのレンダリングサイクルをバイパスする可能性があります。 - 動的なUIでは困難: 入力値の即時値に依存する動的なUI(ユーザーが入力するように文字数を表示するなど)の構築は、より煩雑です。
 
どちらを選択するか
- 
制御コンポーネント (
useState): これは、ほとんどのReactフォームで推奨されるアプローチです。宣言的なUIと明示的なデータフローというReactの哲学によく合致します。以下が必要な場合に使用します。- リアルタイムの入力検証。
 - 入力値に基づく条件付きUIロジック。
 - 動的な入力フィールドと複雑なフォームインタラクション。
 - グローバル状態管理や外部データソースのためのReact状態とのシームレスな統合。
 
 - 
非制御コンポーネント (
useRef): よりシンプルなフォームや特定の例外的なケースでは、非制御コンポーネントを検討してください。- 最終的な入力値が送信時にのみ必要な非常に基本的なフォームの場合。
 useStateの再レンダリングが実際にボトルネックとなる、非常に頻繁な更新を伴うパフォーマンス重視の状況(ただし、典型的なフォームではこれはまれです)。- 直接DOMアクセスを期待するレガシー非Reactコードや特定のサードパーティライブラリとの統合時。
 - セキュリティ上の制限によりプログラムでの値の設定ができないため、ほぼ常に非制御となるファイル入力要素の最終手段として。
 
 
結論
Reactフォームの管理においてuseState(制御)とuseRef(非制御)のどちらを選択するかは、制御、柔軟性、パフォーマンストレードオフになります。非制御コンポーネントは最小限のボイラープレートと潜在的に少ない再レンダリングを提供しますが、Reactの宣言的なパワーの多くを犠牲にし、複雑なインタラクションを困難にします。制御コンポーネントは、より明示的なコードが少し必要ですが、フォームを処理するための堅牢で柔軟でReactらしい方法を提供し、ほとんどのアプリケーションでデフォルトで、そして一般的に優れた選択肢となります。本質的に、制御コンポーネントはReactの方法を採用し、ユーザー入力を処理するための比類のないパワーと予測可能性を提供します。