ReactでuseRefを使う場面は多くの開発者にとって身近です。しかし「いつ使うか」「どこで使うべきではないか」を誤ると、バグの原因になったり可読性や予期せぬ挙動を招いたりします。この記事では使い方の基本から、非推奨のパターンまで、読み手が「React useRef 使い方 非推奨」というキーワードで求める意図をくみ取り、理解と実践に役立つ内容を網羅します。
目次
React useRef 使い方 非推奨パターンと基本的な使い方を比較
まずはuseRefの基本的な使い方を理解し、それと比べて非推奨とされるパターンがなぜ問題になるのかを明らかにします。useRefはレンダーに影響しない値を保持するために使うフックですが、誤用するとReactのレンダーやライフサイクルの原則を壊します。
基本的な使い方:DOM参照やタイマーIDの保存
useRefは主に、DOM要素へ直接アクセスしたいときや、setInterval/setTimeoutのIDなど、コンポーネントの表示には影響しない情報を保持したいときに適しています。初期値を設定し、currentプロパティを介してアクセスし変更します。stateとは異なり、currentを更新しても再レンダーが発生しません。これは入力フィールドのフォーカス設定やメディア再生の操作などに有効です。
不適切なドメイン:レンダー中のcurrent読み書き
Reactの公式ルールでは、レンダー中にref.currentを読んだり書いたりすることは非推奨です。それはコンポーネント関数が純粋関数であるという前提を破壊し、レンダー結果の予測性を損ないます。レンダー中の読み書きは、再レンダーを引き起こさないために結果の更新が画面に反映されず、開発中のStrict Modeで警告が出る原因になります。
レンダーに影響する値をrefで管理する誤用
表示内容に関わる情報(入力値、チェック状態、UIの表示非表示など)をuseRefで管理するのは誤りです。これらはstateで管理すべきです。stateを使えば更新時にレンダーが起こるため表示が同期します。refに任せるとUIが遅れたり、意図しない状態になることがあります。
React useRef 非推奨となる具体的なパターンとその理由
ここでは、最新の情報を元に、useRefを使ううえで避けるべき具体的な非推奨パターンを紹介し、それぞれなぜ避けるべきかを理由とともに説明します。
文字列ref(string refs)の使用
古いクラスコンポーネントで用いられていたref属性に文字列を指定する方法は、Reactの長期的な方針のなかで既に廃止されています。レンダーの過程で文字列refが正しく動作しなかったり、構文解析ツールや型安全性との相性が悪いためです。現在ではコールバックrefまたはuseRef/createRefを使うことが推奨され、文字列refはReact 19で完全に削除される方向にあります。
React.createRefの関数コンポーネント内での使用
関数コンポーネントでReact.createRefを使うのは非推奨です。createRefは毎レンダーで新しいrefオブジェクトを生成するため、refの安定性が失われ、意図しない再マウントやイベントの紐付けずれを生じる可能性があります。関数コンポーネントではuseRefが代替手段として安定していて効率的です。
MutableRefObject型の古いタイプの利用
TypeScriptでuseRefを使う際、RefObjectが現在の標準型であり、MutableRefObject型は後方互換性のために残されているものです。MutableRefObjectだけを使っていたり、型の曖昧な初期値を与えると、currentプロパティにnullが入る可能性の扱いが甘くなり、型安全性が損なわれることがあります。
useRefを適切に使うヒントとベストプラクティス
非推奨パターンを避けながら、useRefを効果的に使う方法を具体的なヒントとともに紹介します。UIやロジックの可読性やメンテナンス性を保つための設計指針になります。
値がUIに影響する場合はuseStateで管理する
もし値を画面に表示したり、レンダリング条件にしたりするなら、useStateを使うべきです。stateが変わるとReactが再レンダーを行うため表示と状態が一致します。これにより見た目のズレや意図しないUIの残像などを防げます。
effects/イベントハンドラ内でのref操作
ref.currentの読み書きは、レンダー中ではなくuseEffectやuseLayoutEffect、またはイベントハンドラ内で行うのが安全です。これによりDOM要素が実際に存在するタイミングで操作ができ、Reactのライフサイクルを侵害しません。特にStrict ModeやConcurrentレンダリング対応の場面で重要です。
大きなオブジェクトや複雑な構造をrefに保存しない
refはどんな型の値でもcurrentに保持できますが、大きなオブジェクトや複雑な構造を保存すると、状態やロジックが参照の変化に追随できず、デバッグが難しくなります。可能であればstate、またはコンテキストや外部ストアを使って整理することが望ましいです。
比較:state vs ref vs context vs observableな外部ストア
処理対象のデータがどのような場合にどの手段を選ぶべきかを比較表で整理します。適切な選択はバグを減らし、パフォーマンスと可読性を改善します。
| 用途/特徴 | useState | useRef | contextや外部ストア |
|---|---|---|---|
| 画面表示に関わる値 | ✅ 状態変化で再レンダーが発生 | ❌ 値更新でレンダーされないため表示とずれる | ✅ コンポーネント階層をまたぐ共有が可能 |
| 非可視かつ頻繁に変わる内部値(タイマーIDなど) | ❌ 再レンダーがコストになる | ✅ 再レンダーなしで安全に保持できる | 用途によるが過剰になることも |
| 値の共有がコンポーネントを越える場合 | ❌ propsドリリングが必要になることがある | ❌ ローカルに閉じておきたい値には適さない | ✅ Contextや外部ストアで図式化しやすい |
| 型安全性と予測可能性の確保 | ✅ 初期値と型が明示できる | 注意が必要:initialValueやnull扱いなど | ✅ より構造的に設計可能 |
最新のReactバージョンでのuseRefに関する変更点と注意点
Reactや型定義ライブラリのアップデートに伴い、useRef周りのルールも少しずつ進化しています。最新情報に基づく変更点と、それにより注意すべきことを整理します。
MutableRefObjectの非推奨扱い
TypeScript用の型定義では、MutableRefObjectが互換性維持のためだけに残されており、標準的にはRefObjectを使うことが推奨されています。RefObjectはcurrentがreadonly扱いの可能性を含んでおり、nullを扱うケースも明示的に設計できるため、安全性が高まります。
string refsの完全削除予定
文字列refはReact 16.3で非推奨とされ、最新バージョンでは警告が出るようになり、将来的なバージョンで完全に削除される見込みです。このため、古いコードベースを扱う際は、速やかにrefcallbackかuseRef/createRefへの移行を検討するべきです。
厳格モード(Strict Mode)やConcurrentモードでの挙動
Strict Modeでは、コンポーネント関数が開発環境で複数回呼ばれることがあります。このときrefの初期化やコールバックrefのクリーンアップがきちんと設定されていないと、メモリリークやnull参照などの問題が現れます。Currentの操作がレンダー中で行われたり、refの初期値が遅延初期化されていなかったりすると、開発時の警告や実行時の不安定さにつながります。
非推奨パターンからの移行ガイド:具体的な置き換え例
大事なのは、非推奨とされる書き方を見つけたときにどう置き換えるかです。ここではstring refsやcreateRef、レンダー中のcurrent操作など、問題パターンからモダンな実装への移行例を示します。
string refs → callback refs or useRef/forwardRef
クラスコンポーネントで ref=”myInput” のように使っていたコードは、callback refsを使って ref={el => this.input = el} のようにするか、createRef/useRefを使った実装に変えるべきです。また、functional componentに ref を前方伝播したい場合は forwardRef を併用します。
関数コンポーネントでのcreateRefの除去
関数コンポーネント内部で createRef を使っているのを発見したら、useRef に置き換えます。createRef は毎レンダー新しいオブジェクトを生成するため、ref が安定していないのが問題です。useRef を使えば、ref オブジェクトはコンポーネント存続中一貫して同じとなります。
レンダー中のref.current操作 → effectハンドラ移動
レンダー中に ref.current を読み書きしているロジックは、useEffect やイベントハンドラ内へ移動させます。レンダー中の current操作は予測不可能なタイミングで old version の current が返されるなどバグを誘発しやすいためです。たとえば入力値の変更など UI更新に伴う処理は effect の依存関係を適切に設定して行います。
よくある疑問と解決策
実際の開発で生じやすい疑問をいくつか取り上げ、それぞれに明確な解決策を示します。これらは検索ユーザーが「何故useRefが非推奨か」などで調べたときに知りたい内容を含みます。
useRefとstate、どちらを使うかの分岐基準は何か
簡単な分岐基準として、「その値が UI に影響するかどうか」です。UI に表示するもの、表示・非表示・スタイル等に関わるものは state、それ以外(DOM要素参照、タイマーID、過去値の比較など)は ref が適切です。変更がレンダリングを引き起こす必要があるなら state を優先します。
callback ref は非推奨か?それともまだ有効か
文字列refと違って、callback ref は現在も有効であり、forwardRef や DOM 要素の取得などで使われています。ただし、正しいクリーンアップ処理や null チェックを含めて設計する必要があります。また documentation上で useRef + useEffect のパターンがより目立つようになっているため、こちらを用いた方が可読性・保守性が高まる場合があります。
新しいrefパターン:Latest Ref Pattern の紹介
最新の実践として、関数をuseRefで保持しつつ useLayoutEffect で最新のコールバックを current にセットするパターンがあります。これにより、古いクロージャを参照し続ける問題を防げます。callbackを更新するとき、自身の再レンダーを引き起こさず ref を最新値に同期させたい場合に有効です。
まとめ
React の useRef は強力なツールですが、使い方を誤ると表示のズレや非可視なバグを招きます。まず「表示に関わる値かどうか」を判断し、UI に影響があるなら useState、それ以外なら ref を使うようにしましょう。レンダー中の current の読み書き、関数コンポーネントでの createRef、string refs といったパターンは避け、useEffect や callback ref、最新の ref パターンを活用することが肝要です。
また、型安全性に関しては RefObject 型を使い、初期値や null の可能性を明示する設計を心がけてください。これまでの非推奨とされてきた使い方からの移行を行いながら、最新の React の流れに沿った設計を取り入れることで、将来的なメンテナンス性と開発効率が向上します。
コメント