useRefの用途を明確に:DOM参照と値保持の使い分け
Reactの useRef
は、要素への参照取得(DOMアクセス)だけでなく、レンダリングを伴わない一時的な値の保持にも使われる多目的なHookです。その柔軟さゆえに、実装者が意図を明示せずに使用すると、レビューアーが混乱する原因になりやすいポイントでもあります。
本稿では、useRef
の用途が曖昧なコードに対して、レビューアーがどのように判断し、構造的な提案や設計意図の明示を求めるべきかを整理します。
useRefが抱える2つの責務
React公式でも言及されているように、useRef
には主に次の2つの用途があります。
- DOMノードへのアクセス
- 状態をレンダリングに影響させずに保持する
前者はref={myRef}
のようにDOM要素に紐づけて使用されるケースで、ユーザー入力やスクロール位置、フォーカス制御などに使われます。後者は、描画に関係しない状態(たとえば前回値の保持やロジックの中間データ)などを記録する用途です。
useRef
はReactの組み込みフックのひとつで、{ current: 値 }
というミュータブルなオブジェクトを生成します。更新しても再レンダリングされない特性を持ち、DOMへの直接アクセスや非描画状態のデータ保持に活用されます。
コード例:用途が曖昧なuseRefの使用
まずは、レビュー観点で見逃しやすい典型的な例を確認しましょう。
function SearchBox() {
const inputRef = useRef();
const isInitialRender = useRef(true);
useEffect(() => {
if (isInitialRender.current) {
isInitialRender.current = false;
return;
}
console.log("検索ロジック実行");
}, [/* 検索条件 */]);
return <input ref={inputRef} />;
}
@Reviewer: `inputRef` はDOM参照、`isInitialRender` は状態保持の用途ですが、命名・コメントなどでそれぞれの責務が明示されていません。用途が異なるuseRefを同列に扱うと、意図が読みづらくなり、構造理解の妨げになります。
useRefの責務を明示しない構造は、レビューアーにとって「これはDOM用?それとも状態保持?」という無駄な読み解きを強要します。
判断基準1:DOMアクセスか否かを見極める
まずは そのrefがDOM要素にバインドされているか をレビューします。ref={}
としてJSXに明示的に渡されていれば、それはDOM参照用のrefであることが明確です。
const inputRef = useRef<HTMLInputElement>(null);
return <input ref={inputRef} />;
それ以外でDOMバインディングがない場合は、内部状態の保持目的である可能性が高くなります。
inputRef
、modalRef
、scrollRef
のように要素名+Refを命名規則とすると責務が読み取りやすくなります。
判断基準2:値保持目的でのuseRefは明示的なコメントを求める
値保持目的のrefに関しては、状態更新を引き起こさないことを設計意図として明示しているかが重要です。特に、以下のようなフラグ管理に使われている場合は、useStateとの使い分けが正当化できているかをレビューしましょう。
const isMounted = useRef(true);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
@Reviewer: `isMounted` は非同期処理中のアンマウント検知フラグとして使用されていますが、コンテキストや注釈がないと `useState` との使い分けが曖昧になります。状態としての意味よりも「トラッキング用途」であることを明記してください。
このように、副作用の制御やフロー制御のために使われるrefについては、その使い方の設計意図をコメントや命名で伝える必要があります。
:責務別に分けたuseRefの構造
次の構造図は、DOM参照と状態保持を明確に分離して設計された構造例です。
このように、useRefの目的が責務ごとに整理されている構造は、レビューの可視性も高く、保守も容易です。
値保持としてのuseRefを使うべき場面とその注意点
値を保持する目的で useRef
を使用する構造は、useState
と異なり再レンダリングを発生させないという特性を活かすもので、パフォーマンスやレンダリング最適化の観点から合理的な選択であることも多いです。
しかし、以下のような誤用にはレビューアーとして注意が必要です。
- ユーザーにとって可視的な状態変化を
useRef
で管理している current
の値更新が副作用としてロジックに影響しているuseRef
を「状態の隠し場所」として使用しており、再レンダリングとの同期性が断絶している
これらの構造はUIの再現性や状態の一貫性を破壊するリスクがあります。
useRefを誤用したコード例
function ToggleBox() {
const isVisible = useRef(false);
const toggle = () => {
isVisible.current = !isVisible.current;
};
return (
<>
<button onClick={toggle}>表示切り替え</button>
{isVisible.current && <div>内容表示</div>}
</>
);
}
@Reviewer: `isVisible` はUI表示に影響する状態であり、本来 `useState` による状態管理が必要です。useRefで変更しても再描画は行われず、UIと状態が不整合になる恐れがあります。
このような構造は一見して動作するように見えますが、実際にはReactの「宣言的UI」という設計方針と乖離しており、バグの温床になります。
状態保持をuseRefで行う正当なケース
一方で、以下のようなケースは useRef
を使う利点がある構造です。
- 前回値の保持
- デバウンス/スロットル処理のタイマーID保存
- 非同期処理中のキャンセル判定
- useEffect内の一時的な値記録
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
@Reviewer: `ref.current` は再レンダリング不要なデータ保持として合理的に使用されています。前回値を記録する構造が明示されており、責務も明確です。
このように、現在のUIに直接関与しない値を保持したい場合に限って、useRefは適切な選択になります。
useRef vs useState:レビュー観点での使い分け基準
レビューアーとしては、「useRefで書かれているけど、これは本当にstateにすべきでないのか?」という観点で常に読み解く必要があります。
次のような判断軸が有効です。
評価観点 | useRefが適切 | useStateが適切 |
---|---|---|
値の変更がUI描画に影響するか | × | ○ |
値の変更に伴って副作用を起こしたいか | △ | ○ |
再レンダリングを避けたいか | ○ | × |
コンポーネント外部からも状態変更されうるか | ○ | ○(コンテキスト化も検討) |
Reactの「状態としての追跡」が必要か | × | ○ |
この観点をもとに、「useRefは再描画を伴わないが、それゆえに状態が可視化されない」という非対称性を常に意識して構造をレビューする必要があります。
チームレビューで使える指摘テンプレート例
以下は、useRefの使い方が曖昧な実装に対して使える、レビュー用の指摘テンプレートです。
@Reviewer: このuseRefの使用目的がDOM参照なのか値保持なのかが読み取りにくいです。構造上、どちらの責務かを明確にするために命名ルール(〜Ref, 〜Flagなど)やコメントによる補足があると設計意図が共有しやすくなります。
@Reviewer: `ref.current` に保存されている値がUI表示や処理分岐に使われているため、状態として `useState` に昇格させた方が構造の透明性が上がります。再描画要否を再検討してください。
このように、useRefの使用が曖昧な構造に対しては、意図の明示を求めることがレビューアーの役割です。
まとめ:useRefの用途が読み取れる構造を設計できているか
useRefは便利である反面、その多用途さが設計意図を曖昧にしやすいという構造上の弱点を持ちます。
レビューアーとしては以下を常に意識し、単に「使っているか」ではなく「使い方が明示されているか」を判断軸に持つことが重要です。
- DOM参照と値保持を命名や構造で明確に分離しているか
ref.current
をUI描画に用いていないか- 状態の一貫性や再描画の必要性を犠牲にしていないか
- useStateとの責務分離が設計レベルで説明可能か