ReactのuseRefフックはDOMの参照やレンダーをトリガーしない値の保持に便利ですが、使い方を誤ると予期せぬバグやパフォーマンス問題を引き起こすことがあります。今回の記事では「React useRef 使い方 非推奨」というキーワードを軸に、最新のReactで避けるべきパターンと、それらを安全に使うための注意点を整理します。具体例や代替案もふんだんに紹介するので、現場で即活かせます。
目次
React useRef 使い方 非推奨となるパターンとその理由
ReactのuseRefを使う際、「非推奨」とされる使い方には共通した特徴があります。主に以下のようなケースがあり、それぞれ理由があります。
レンダリング依存の値をrefで管理する
画面に表示すべき値をrefで管理すると、ref.currentを書き換えても再レンダーが発生せずUIに反映されません。ユーザー側に見える情報はstateを使うべきで、refを代わりに使うのは非推奨です。UIが更新されないため、デバッグが難しくなる傾向があります。
レンダー中にref.currentを読んだり書き換えたりする
コンポーネントの描画フェーズ中にref.currentを読み書きすると、副作用や予測不可能な挙動を引き起こします。Reactのレンダーは純粋関数として扱われることを前提としており、レンダー中の状態変更はその前提を崩すためです。これはStrict Modeでも検出対象となります。
refでDOM外のロジックや状態を代替してしまう
ソートのフラグ、入力の内容、表示/非表示など、DOM操作や内部状態以外のロジックをrefで扱うと可読性が下がります。Reactではこうした状態はstate/propsで宣言的に管理すべきで、refはあくまで「必要最低限の不変値や外部APIのハンドリング」に留めるべきです。
非推奨パターンによる具体的な問題例と影響
非推奨なuseRefの使い方が引き起こす典型的な問題を具体的に見ておきます。それぞれの影響を理解することが、安全な実装の第一歩です。
UIが更新されないバグ
たとえばクリック数を追いたいが、stateではなくrefで管理すると、クリックしても画面上の数値が変わらない現象が起こります。ユーザーには値は増えても表示が変化しないため混乱を招きます。
競合レンダリングや遷移のずれ
ReactのConcurrentモードやStrict Modeでは、描画タイミングやレンダーの重複が行われることがあります。このときレンダー中にrefを書き換えると、一度見たrefと最新のrefが一致しないなどの競合・不整合が起こる可能性があります。
依存配列の誤りと無限ループ
useEffectの依存配列にref.currentを書いてしまうパターンがありますが、ref.currentはミュータブルでリアクティブではないため、変更があってもeffectは再実行されません。誤った理解でstate代用に使うと、効果が発火しない・ループが起こるなど設計ミスになります。
React useRef を安全に使うためのベストプラクティス
非推奨な使い方を避けつつ、useRefを安全に活用するための指針です。読み手が現場で実践できる具体的なポイントを中心に紹介します。
表示が必要な値にはuseStateを使う
画面に表示する値や、ユーザー操作で変化して再レンダーが必要な場合はuseStateを使うべきです。stateは変更時に自動的に再レンダーが行われUIが更新されるため、可視的な情報の管理には最も適しています。
レンダーフェーズではref操作を避ける
ref.currentはレンダー中には基本的に読み書きしないことが鉄則です。操作が必要なタイミングはuseEffect内またはイベントハンドラ内に限定することで副作用を制御し、描画の純粋性を保てます。
forwardRefを活用して親子間でのDOM操作を明確に分離する
カスタムコンポーネントにrefプロパティを渡したい場合はforwardRefを使って、内部のDOM要素との橋渡しを明確にします。こうするとコンポーネントの再利用性が高まり、親子間の責任領域がより明瞭になります。
ミュータブルな値の初期化は遅延または条件付きで
初期値に複雑なオブジェクト生成やクラスインスタンスを使うなら、useRef(null)で初期化し、最初のレンダー後や条件付きでcurrentを設定するパターンが好ましいです。こうすると初期レンダーのコストを抑え、副作用もコントロールできます。
代替手段:非推奨な使い方を避ける設計戦略
問題を回避するための設計戦略として、useRef を使わずに済ませる・もしくは併用することでより堅牢なコードが書ける方法があります。
useStateとuseRefの併用
UI表示に関わる値はstateで、それ以外をrefで持つというパターンです。stateでレンダー制御しつつ、refでタイマーIDや先行値(previous値)、外部APIオブジェクトなどを保持することでロジックを分けると管理が容易になります。
カスタムフックで抽象化する
よく使われるrefの操作(例:スクロール位置の追跡、外部イベントリスナー管理、制御不能なDOM操作など)はカスタムフックとして切り出すと良いです。カスタムフック内で安全性(nullチェック、クリーンアップなど)を保証でき、呼び出し元のコードをシンプルに保てます。
代替APIやHooksの活用
UI状態管理や副作用の管理にはuseState、useReducer、useEffectなどのReact標準APIを優先します。また、外部ストアの値を扱いたい場合は専用のHooksや同期ストアAPIを使うことで、ミュータブルで反応性のないrefに頼る必要が減ります。
最新情報から見たReact useRefの非推奨傾向の変化
Reactの最新情報を追うと、useRefについてのガイドラインも微調整されています。過去の使い方が非推奨になりつつあり、より宣言的な代替手段が推奨されるケースが増えています。
React公式が強調する純粋性とレンダーの純粋関数性
公式文書の中で、レンダー関数はプロップスやステート、コンテキストから返すJSXが同じ入力に対して常に同じ出力を返すべきという規約が繰り返し述べられています。レンダー中にrefに依存するコードはこの純粋性を壊すため、最新の推奨設計では避けるべきとされています。
Concurrentモードや新しいレンダリング戦略への対応
レンダーの順序やタイミングが非同期的または予測不可能になる並行レンダリングモードでは、レンダー中にrefを操作すると一貫性や可用性で問題が起こりやすくなります。そのため、最新のReactではrefの操作は副作用フック内に限定する方針が強まっています。
型安全性とTypeScriptでの利用の注意点
TypeScriptを使うプロジェクトでは、useRefに型を付ける際にnull許容型にすることや、currentが確実に存在することを保証するコードのパターンが重視されています。初期値をnullとし、後に確実にDOMノードが存在することを条件付きで操作するよう安全策が標準となっています。
実践的なコード例:safe な useRef の使い方と非推奨例の比較
具体的なコード例を比べてみることで、非推奨なパターンと安全なパターンが肌で理解できます。ここでは2つの例を比較します。
| 非推奨なパターン | 安全なパターン |
function Counter() {
|
function Counter() {
|
上記の比較でわかるように、UI表示にrefを使ってしまうとユーザーに変化が伝わらずバグの原因となります。安全なパターンではstateを使ってUI更新を制御しています。
チェックリスト:useRefを使う前の確認事項
useRefを使う前に以下の点を確かめて、非推奨な使い方を未然に防ぎましょう。
- その値がUI描画に必要かどうかを確認する。必要ならstateで管理する
- レンダー中にref.currentを読み書きしていないか
- DOMノードとのやり取りが副作用フックやイベントハンドラ内に限定されているか
- 初期値に重い処理やオブジェクト生成を含んでいないか。必要なら条件付きで初期化する
- TypeScriptなどで型安全性を確保しているか(null許容、存在チェックなど)
まとめ
ReactでuseRefを使う際、「非推奨な使い方」が何を指すのかを理解することが安全な実装の第一歩です。表示に関わる値をrefで扱うこと、レンダー中にrefを読み書きすること、ロジックをstateではなくrefで代替することなどは避けるべきです。代替手段としてuseStateやuseReducer、カスタムフックを活用し、forwardRefでコンポーネント設計を整理し、TypeScriptで型安全性を担保することも重要です。
これらのベストプラクティスを踏まえてuseRefを活用すれば、コードの可読性や予測可能性が向上し、バグの混入を防げます。安全に実装されたコードは、保守性や拡張性も高くなりますので、今回紹介したチェックポイントを現場でぜひ取り入れてみてください。
コメント