ReactでuseRefを使ってDOM要素にアクセスしたり値を保持したりする例は多く見られますが、それがいつも最良の方法とは限りません。特に、設計の一貫性・レンダリング挙動・パフォーマンスという観点から、useRefの使いどころと非推奨とされるケースを正しく理解することが重要です。この記事ではuseRefの基本から、なぜ「非推奨」と呼ばれることがあるのか、代替手段、そして最新情報を踏まえた正しいDOM操作方法まで詳しく解説します。
目次
React useRef 使い方 非推奨:その概念と誤解されがちなポイント
Reactの中でuseRefというHookはrenderに直接関係しないデータやDOMノードへの参照を持つために使われます。通常、値が変化してもUIには影響しない、いわば非宣言的な目的で使われることが多いです。しかし、その使い方が誤ると、予期せぬレンダリングタイミングの問題やUIの矛盾を招くことになります。ここでは、useRefの機能・誤解・非推奨とされる理由について整理します。
useRefとは何か:機能と基本動作
useRefはReactにおけるHookの一種で、初期値を持つオブジェクトを返します。そのオブジェクトはプロパティcurrentを持ち、レンダリング間で持続する値を保持できる点が特徴です。DOM要素への参照や、タイマーID・外部APIとのやりとりのように、UI描画とは直接関係ない値の保存に有効です。
useRefがレンダリングに影響しないことの誤解
useRefを更新してもReactは再レンダリングを自動では行いません。stateを使えばUIが変化すると期待できますが、ref.currentを変えてもUIには反映されません。そのため、値が見える形で表示される必要がある場合はstateを使うべきです。またrefを描画中に読み書きすることは、レンダリングの純粋性を壊し、同じレンダリングパスで異なる値を読む「ティアリング」などの問題を引き起こす可能性があります。
なぜuseRefの使い方が「非推奨」と言われるか
主な理由として以下が挙げられます。まず、UI出力に影響する値をrefで扱うと、描画がコントロールできずバグが発生しやすくなります。次に、DOM操作を直接行うことによるReactの仮想DOMとの非整合性です。さらに、複雑な構成ではuseRefの多用がコードの可読性や保守性を低下させる原因となります。
React useRef 使い方 非推奨とされる具体的なケースと問題点
useRefが非推奨とされるのは抽象的な話ではなく、特定のケースにおいて実際に問題を引き起こすためです。ここでは最新の実践例やドキュメントに基づいて、どのようなコードや状況が問題になるかを具体的に見ていきます。
render中にref.currentを読み書きするパターン
コンポーネントのレンダリング中にref.currentを読み書きしてしまうと、Reactの設計するレンダリングの純粋性が損なわれます。このような実装はタイミングに依存し、予測不可能な動作をもたらすことがあります。Reactは並列レンダリングや遅延レンダリングを行うことがあり、同じレンダリングパスでref.currentが途中で変わることによる矛盾が発生します。
UIに表示される値をrefで管理する誤用
例えば、クリック回数や入力フィールドの表示内容など、UIに反映される値をrefで管理すると、refが更新されても画面に反映されずユーザーの期待と異なる体験を与えてしまいます。これらのケースではstateを使うことで解決でき、Reactの宣言的な設計に沿ったコードになります。
DOMを直接操作するImperativeなコードとの併用
focus、scroll、アニメーション制御などでDOM操作が必要な状況がありますが、その中でもReactが管理している部分を手動で操作することは状態の不整合や壊れた表示を招くことがあります。特にDOMノードをremoveしたりinnerHTMLを書き換えるような操作は、Reactの仮想DOMと本物のDOMの状態に齟齬が出る原因になります。
React useRef 使い方 非推奨を避ける代替手段とベストプラクティス
非推奨とされる使い方を避け、Reactの設計原則に沿ったコードを書くためにはどのような代替手段があるのか、具体的なベストプラクティスを示します。DOM操作や状態管理に対してより安全で可読性・保守性の高い方法を選ぶことが重要です。
状態に関わる値はuseStateやuseReducerを使う
UIに影響するような値(入力値、表示状態、トグルなど)はuseStateまたはuseReducerを使って管理するのが原則です。これにより値の変更がレンダリング処理に反映され、Reactの再現可能性・純粋性が保たれます。useReducerは複雑な状態操作があるときに特に有効です。
DOM要素への参照が必要な時の安全な方法
フォーカスやスクロール、計測などDOMノードに対する参照が必要な場面では、useRefを使うことがありますが、読み書きはイベントハンドラやuseEffect/useLayoutEffect内で行うのが望ましいです。レンダリング中に直接触らないことで不整合のリスクを減らすことができます。
forwardRefやuseImperativeHandleの活用
子コンポーネントのDOM要素やインターフェースを親が操作する必要がある場合、forwardRefとuseImperativeHandleを併用することでインターフェースを限定し、安全に制御できる方法が確立されています。この手法は意図せぬ副作用や不必要な依存を減らすことができます。
最新情報:ReactのレンダリングモードとConcurrent ModeにおけるuseRefの注意点
ReactではConcurrent Modeなど並行性を持ったレンダリングが導入されており、それに伴ってuseRefの振る舞いにも注目が集まっています。最新のReactの設計とこれらモードでのtie-upについて理解しておくことが、非推奨を避けるためには不可欠です。
Strict Modeによる2回呼び出しと副作用の注意
Strict Modeではコンポーネント関数が意図的に2回呼び出されることがあります。この際useRefは初期化時の処理を2回行うケースがありますが、初期値のみが反映される設計になっています。ここで副作用的な処理を初期化で行うと、予期せぬリソース消費や動作が発生することがあります。
並行レンダリング(Concurrent Mode)での不整合リスク
Reactive UIが分割レンダリングされると、前半部分でref.currentがある値を読み、後半部分で別のイベントで更新されたref.currentを読むことがあります。同じレンダリングパスで異なる値をもつ状態はUIの一貫性を保てなくなる原因となります。
最新のLintルールと開発ツールによる警告の活用
ReactエコシステムにはuseRefの不適切な使用を検出するLintプラグインやルールが存在します。これらを導入することでレンダリング中の読み書きや宣言的でないDOM操作などを未然に防ぎ、コード品質を高めることができます。
React useRef 使い方 非推奨との比較:正しいDOM操作の具体例
ここまでの内容を踏まえて、誤ったuseRefの使い方と正しいDOM操作を比較することで理解を深めます。サンプルコードや表を使ってどちらがなぜ好ましいかを明確に示します。
誤用サンプル:レンダリングでrefを使うパターン
以下は、レンダリング中にref.currentを使ってUIに影響を及ぼそうとする例です。このパターンは非推奨であり、不安定なUIや予期せぬバグを引き起こす可能性があります。レンダリング関数の中でref.currentを読み書きし、それによって表示内容を決定している場合がそれに該当します。
正しいサンプル:stateとuseEffectを使ったパターン
こちらは正しいアプローチです。UIに表示される値はuseStateで管理し、DOM操作や非表示要素の参照はuseEffect内で行います。また、必要ならuseRefでDOM要素への参照を保持し、forwardRefやuseImperativeHandleで子要素とのやりとりを安全に制御します。
コード比較表
| 誤用(非推奨) | 推奨される使い方 |
|---|---|
| UI表示にref.currentを使う | UI表示はstateで行い、refはロジック用 |
| レンダリング中にrefを読み書き | useEffectやイベントハンドラ内で操作する |
| DOMを直接removeしたりinnerHTML操作したりする | Reactの仮想DOMと統一的に制御する |
実践的ガイド:ReactでDOM操作を正しく行う方法
実践の現場で使える具体的な手法を紹介します。この手法を使えばDOM操作とReactの宣言的な設計を両立させつつ、useRefの非推奨な使い方を避けることができます。
フォーカス管理・スクロール操作の正しい実装
入力フォームの初期フォーカスやスクロール位置の制御はuseRefで取得したrefを使いますが、focusやscroll呼び出しはrender後、つまりuseEffectまたはuseLayoutEffect内で行うのが安全です。こうすることでDOMが確実に存在し、Reactが引き起こす再描画のタイミングを逸脱しません。
前回のプロップやステート値を参照するためのカスタムフック
以前のステートやプロップを比較したり保存したい場合、useRefを使ったカスタムフックがよく使われます。ただしその値を画面に表示したり描画内容に使ったりしないことが前提です。そうすることで描画の安定性が保たれます。
外部APIやタイマー、イベントリスナの管理
タイマーのIDやイベントリスナなどReactのレンダリングとは無関係な値を保存するのにuseRefは適しています。数値・オブジェクト・関数などの可変値をcurrentに持たせ、必要な時にclearやunsubscribeを使ってクリーンに処理します。
まとめ
ReactでuseRefを使うこと自体は間違いではなく、フォーカス制御・スクロール・外部APIの管理など、レンダリングに直接関係しない場面で非常に強力です。しかしUIに見える値や描画ロジックに影響を与える値をrefで管理することは非推奨であり、再レンダリングや並行レンダリング、レンダリング中の読み書きによってバグや表示の矛盾を引き起こす可能性があります。
ベストプラクティスとしては、UI表示する値はstate/reducerで管理し、DOM参照・imperativeな操作はuseRef+useEffectまたはforwardRef等を適切に組み合わせて行うことです。Lintルールを導入し、Reactの設計指針を尊重するコードを書くことで、より安全で予測可能なアプリケーションを構築できます。
コメント