😜

いつから制御/非制御コンポーネントはフォームの話だと錯覚していた

2024/07/24に公開

Reactのフォームの話で見かけることの多い制御コンポーネントと非制御コンポーネント。実は現Reactドキュメントだと定義が変わっているんです。旧Reactドキュメントと現Reactドキュメントの定義を整理し、なぜ定義が変更されたのか考察していきます。

旧Reactドキュメント

旧Reactドキュメントでは以下のように定義されています。

React によって値が制御される入力フォーム要素は「制御されたコンポーネント」と呼ばれます。

https://ja.legacy.reactjs.org/docs/forms.html

非制御コンポーネント (uncontrolled component) はその代替となるものであり、フォームデータを DOM 自身が扱います。

https://ja.legacy.reactjs.org/docs/uncontrolled-components.html

おそらく多くの方の制御コンポーネント・非制御コンポーネントの認識はこちらだと思います。フォームデータをReactで制御(状態管理)しているかという観点に基づき定義されている概念です。

現Reactドキュメント

現Reactドキュメントでは以下のように定義されています。

一般的に、ローカル state を持つコンポーネントを “非制御 (uncontrolled)” であると呼びます。

対照的に、重要な情報がローカル state ではなく props によって駆動されるとき、コンポーネントは “制御された (controlled)” ものと呼ばれることがあります。

https://ja.react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components

つまり、現Reactドキュメントでは制御コンポーネンを親コンポーネントに制御される(propsを使う)、非制御コンポーネントを親コンポーネントに制御されない(stateを使う)という観点から定義されています。

なぜ定義が変わったのか

旧Reactドキュメントでの定義が廃止された理由

旧Reactドキュメントが書かれていた時代は制御コンポーネントによってフォームデータを管理することが推奨されていました。なぜなら、フォームデータを常にReactの管理下に置くことでインスタントフィールド検証などのフォームデータと同期したUIを作ることができたからです。

https://goshacmd.com/controlled-vs-uncontrolled-inputs-react/

しかし、制御コンポーネントはフォームデータを更新するたびに再レンダリングが発火するため、パフォーマンスの面で懸念されるようになりました。そこで注目されたのがReact Hook Formです。React Hook Formは非制御コンポーネントなのでパフォーマンスもよく、Subscriptionベースの状態管理でフォームデータとUIの同期を可能にしました。

https://zenn.dev/counterworks/articles/react-hook-form-subscription

React Hook Formの台頭により制御コンポーネントはレガシーとなり、現Reactドキュメントに移行する際に廃止されたのだろうと思います。

現Reactドキュメントで新たな定義が記載された理由

※ 妄想が多く含まれます。

現Reactドキュメントでの制御コンポーネントと非制御コンポーネントは以下のように認識されています。

実際には、“制御された”、“非制御” は技術用語として厳密なものではありません。各コンポーネントは通常、ローカルな state と props の両方を、混在して持つものです。しかし、コンポーネントがどう設計されるか、どんな機能を持つかについて話す際には、このような考え方が役に立つでしょう。

コンポーネントを書くときには、その中のどの情報を(props で)制御し、どの情報を(state を使うことで)制御しないのかを検討してください。

https://ja.react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components

同じ用語で新たな定義を付与することは混乱を生みやすいので、このようにドキュメントに記載されたことは嘆かわしく感じています。しかし、視点を変えてこの文章に意義を見出すとすればReact Server Componentsが関係してくるのではないでしょうか。

React Server Components(以下、RSC)は従来のReactをClient Componentsとし、新たにバンドル前に事前にレンダーされるServer Componentsを導入しました。Client CoponentsとServer Componentsでできることは全く異なっています。

Server Component Client Component
データを取得 ×
バックエンドリソースにアクセスする(直接) ×
機密情報(アクセストークン、APIキーなど)をサーバー上に保存する ×
サーバーへの大きな依存関係を維持し、クライアント側のJavaScriptを削減する ×
インタラクティブ機能とイベントリスナーを追加する(onClick()onChange()など) ×
状態とライフサイクル効果を使用する(useState()useReducer()useEffect()など) ×
ブラウザ専用のAPIを使用する ×
状態、効果、またはブラウザ専用のAPIに依存するカスタムフックを使用する ×
Reactクラスコンポーネントを使用する ×

RSCの文脈でstateを使う、つまり非制御コンポーネントであるならば、そのコンポーネントは必ずClient Componentsです。これは認知負荷の低減に少しは寄与するかもしれません。

まとめ

制御コンポーネント 非制御コンポーネント
stateで管理 DOMで管理
propsで管理 stateで管理

テック記事ではフォームでの定義を使用し、React関連の公式ドキュメントでは再定義されたものを使用しているという現状です。私自身、React Hook Form + MUIを調査する過程で運良く気づいたものでした。本記事がドキュメントや記事を読む皆様の助けになることを願います。

あとがき

個人的に現Reactドキュメントに刷新する際にフォームに関する記載を削除したことは妥当だと感じていますが、現Reactドキュメントにて再定義したことに納得できていません。有識者の方、教えてください。

Discussion