useRef.current は、Reactの再レンダリングとは無関係に値の読み書きができる特殊な領域です。この特性を利用して、useEffect の依存配列に ref.current を含めようとするコードを見かけることがあります。

一見「安定的で変わらない値を参照する設計」として合理的に見えるこの手法ですが、Reactの依存配列評価モデルに対する理解不足によって、かえって誤動作や設計不整合を招くケースが多く、レビューでは慎重な判断が求められます。

誘惑:useRefの値を依存に含めたくなるケース

ref.currentを依存に含める構造
const ref = useRef(0);

useEffect(() => {
  doSomething(ref.current);
}, [ref.current]);
@Reviewer
`ref.current` はプリミティブな値であっても `useEffect` の依存には適しません。Reactの依存評価は「参照が変化したか」であって「内部のプロパティが変化したか」ではないため、意図通りに動作しない恐れがあります。

このように記述しても、ref.current が更新されても useEffect は再実行されません。これは依存配列が ref.current の値ではなく、評価時の値のコピーになるためです。

Reactの依存配列の評価基準

Reactの依存配列は「参照が変化したか(=== がfalse)」を評価基準にしており、オブジェクトの内部プロパティが変わってもそのオブジェクト自身の参照が変わっていなければ、依存とは見なされません。

つまり:

  • useState の更新は自動的に再レンダリングと副作用を引き起こす
  • useRef.current の変更はReactの監視対象ではない

この仕組みを理解していないまま ref.current を依存に含めると、「なぜこの useEffect が発火しないのか?」という誤解を生みます。

意図的な依存ではなく“状態の観測”であるべき

もし ref.current の変化に応じて useEffect を再実行したいという要件がある場合、それは本来「状態」や「イベント」によって制御されるべきであり、refを依存にするのは根本的に適切ではありません。

refの変化で副作用を起こす代替構造
const [scrollY, setScrollY] = useState(0);
const scrollRef = useRef();

useEffect(() => {
  const handleScroll = () => {
    setScrollY(scrollRef.current?.scrollTop || 0);
  };
  scrollRef.current?.addEventListener("scroll", handleScroll);
  return () => scrollRef.current?.removeEventListener("scroll", handleScroll);
}, []);
@Reviewer
`ref.current` の内部値がトリガーになる場合は、状態として切り出し `setScrollY` のように更新をトリガーにした設計が適しています。ref自体を依存にしても、Reactはその内部変化を追跡しません。

このように、refの変化を監視したいのであれば、それはstateやイベントによる構造で行うべきです。

依存配列にrefを含めることが必要な唯一のパターン

useRef 自体の参照がコンポーネント外から更新されうるような特殊な構造(例:カスタムフック内のref変更通知など)であれば、refオブジェクト自体を依存に含めることは構文的には意味があります。

ref自体を依存に持つ構造
useEffect(() => {
  console.log("refが切り替わった");
}, [ref]);

しかしこれは ref.current の値変化を監視しているわけではなく、refオブジェクトそのもの(identity)に依存しているだけです。内部値の追跡ではないという点を理解していなければ、誤用を助長します。

よくある誤用パターンとレビュー例

const isFirstRender = useRef(true);

useEffect(() => {
  if (isFirstRender.current) {
    isFirstRender.current = false;
    return;
  }
  doSomething();
}, [isFirstRender.current]);
@Reviewer
`ref.current` の変化をトリガーにしたいように見えますが、Reactはこのプロパティを依存として評価しないため、副作用の再実行はされません。依存としての役割を果たせず、誤解を招きます。

このような構造では、依存ではなく「制御用のフラグ」が useRef に隠れているため、意図が伝わりづらく、レビューの際には設計の意図を明確にする必要があります。

まとめ:refを依存にしたくなった時に問うべきこと

useRefを依存にしたくなった時のチェックリスト
  • ref.current の値変化をトリガーにしたいのか? → それはstate化すべき
  • 参照自体が切り替わる設計になっているのか? → 依存に含める理由がある
  • ref を使うこと自体が副作用を隠していないか? → 設計の明示が必要
  • 状態か、イベントか、別の設計方法が適していないか? → 再検討

refはReactの状態管理の外側にあるため、その値をトリガーにロジックを構築しようとすると、Reactのモデルに反した設計になりがちです。レビューアーとしては、「なぜそれをrefにしたのか」「なぜそれを依存にしたいのか」を常に問い直し、構造的に伝わる設計への改善を促す必要があります。