皆さん、こんにちは!Reactの世界へようこそ!
Reactの海を航海していると、時々「あれ?これってどう使うんだっけ?」「この部品は、あっちの部品とどう組み合わせるのが正解なんだろう?」と、ちょっと立ち止まってしまう瞬間がありますよね。特に、`useEffect` と `useRef` の関係は、多くの人が「うーん…」と首をひねるポイントの一つじゃないでしょうか。
「`useRef` って、`useEffect` の依存配列に入れるべきなんだっけ?」「入れないと何か問題が起きるのかな?」
大丈夫ですよ。この疑問、私も現場で何度も耳にしてきましたし、むしろ、ここに疑問を感じるあなたは、Reactの奥深さに一歩足を踏み入れている証拠です。今日は、伝説のチーフアーキテクトである私が、このモヤモヤをスッキリさせるために、皆さんの手を取ってじっくりと解説していきますね。
難しい専門用語はなるべく使わず、身近な例え話を交えながら、「なるほど!」と膝を打つような解説をお届けします。さあ、ご一緒に見ていきましょう!
—
はじめに:`useRef` と `useEffect`、その疑問にズバリ答えます!
今日のテーマは、まさにこれです。
「`useRef` を `useEffect` の依存配列に含めるべきか否か」
結論から先に言ってしまうと、基本的には「含めない」 のが正解です。
「え、そうなの!?」と思った方もいるかもしれませんね。なぜそうなのか、その理由をこれから丁寧に紐解いていきましょう。
そのためにはまず、それぞれの役割をしっかりおさらいするところから始めましょう。基礎を固めることが、どんな複雑な問題も解決する一番の近道ですからね。
—
まずは基本のおさらい:`useRef` は「忘れん坊ノート」
`useRef` って、一体何のためにあるんでしょう?
私はよく、`useRef` を「忘れん坊ノート」と呼んでいます。
皆さんも、何か大事なメモを一時的に取っておきたい時、手元にノートを置きませんか?
Reactの世界でコンポーネントが再レンダリング(画面が描き直されること)されるたびに、そのコンポーネントの中で使われていた変数たちは、基本的に一度「忘れられて」しまいます。まるで、新しいページに描き直されるたびに、前のページのメモが消えてしまうノートのようです。
でも、`useRef` を使うと、その「忘れん坊ノート」の中に、画面が何回描き直されても消えない、大事なメモ を残しておくことができるんです。
`useRef` の役割:画面に影響せず値を保持
具体的にどんなメモを残すのかというと、主に次の2つの役割があります。
1. DOM要素へのアクセス: 例えば、HTMLの `` タグに直接フォーカスを当てたい時や、スクロール位置を操作したい時など。Reactの世界の外にある要素を直接いじるイメージです。
2. 再レンダリングに左右されない値の保持: コンポーネントが何回再レンダリングされても、その値を忘れてほしくない、でも、その値が変わっても画面を再描画する必要はない、というような一時的な値を保持するのに使います。タイマーのIDなんかが代表的ですね。
`useRef()` を呼び出すと、`{ current: 初期値 }` の形をした「箱」が一つ手に入ります。この「箱」は、コンポーネントが再レンダリングされても、ずっと同じ箱であり続けます。そして、箱の中身(`current` の値)は、いつでも好きな時に変更できます。
ここがポイント:値が変わっても再レンダリングしない!
ここが今日一番大事なポイントです!
`useRef` の `current` プロパティの値を変更しても、コンポーネントは再レンダリングされません。
例えるなら、皆さんの「忘れん坊ノート」にメモを書き足したり消したりしても、ノート自体が新しくなるわけではありませんし、そのメモが画面に表示されているわけでもありませんよね?ただ、心の中で「あ、メモを書き換えたな」と認識するだけ。`useRef` は、そんな「心の中のメモ」のような存在なんです。
—
おさらいその2:`useEffect` は「監視役の番犬」
次に `useEffect` についてです。これはもう、Reactの心臓部と言っても過言ではありませんね。
`useEffect` は、コンポーネントのレンダリング(描画)が終わった後に、何か特別な処理(「副作用」と呼びます)を行いたい時に使います。
依存配列の役割:何を監視するのか?
`useEffect` には、`useEffect(コールバック関数, [依存配列])` のように、二つ目の引数に「依存配列」というものを渡すことができます。
この依存配列は、まるで「監視役の番犬」のようなものです。
番犬は、依存配列の中に指定されたものが「変化した」ことを検知すると、「ワンッ!」と吠えて、`useEffect` の中の処理(コールバック関数)をもう一度実行します。
- もし依存配列が空っぽ `[]` なら、「誰も監視していないよ」という意味で、コンポーネントが最初に画面に現れた時と、画面から消える直前(クリーンアップ関数があれば)にだけ実行されます。
- もし依存配列を省略すると、コンポーネントが再レンダリングされるたびに、中の処理が実行されちゃいます。これは、番犬が常に「何か変わったか!?」とピーピーと騒いでいるようなもので、多くの場合、パフォーマンスを悪くしてしまうので注意が必要です。
—
いよいよ本題!`useRef` を依存配列に入れるべきか、否か?
さあ、ここまでの知識を元に、今日の核心に迫りましょう!
結論:基本的には「NO」です!
「`useRef` を `useEffect` の依存配列に含めるべきか?」という問いに対して、私の経験上、そしてReactの設計思想から見ても、基本的には「含めない」のが正解 です。
その理由:「引き出し自体」は変わらないから
先ほどの「忘れん坊ノート」の例えを思い出してください。
`useRef()` を呼び出すと、`{ current: 初期値 }` という形の「箱」が手に入ると言いましたね。この「箱」自体は、コンポーネントが再レンダリングされても、ずっと同じ箱であり続けます。
`useEffect` の「監視役の番犬」は、依存配列に入っているものが「新しいものに変わった」ことを検知して吠えます。しかし、`useRef` が返す「箱」自体は、コンポーネントのライフサイクルを通じて「変わらない」んです。
つまり、番犬に「この箱を監視してくれ!」と頼んでも、箱自体は常に同じなので、番犬は「何も変わってないな」と思って、いつまで経っても吠えないわけです。これでは、番犬が期待通りに仕事をしてくれませんよね。
もう少し深く:`useRef` オブジェクトは「常に同じ場所にある」
技術的な言葉で言うと、`useRef` が返すオブジェクト(参照)は、コンポーネントのライフサイクルを通して安定した同一のオブジェクトを指します。つまり、コンポーネントが再レンダリングされても、メモリ上の同じ場所にあるオブジェクトを指し続けるということです。
`useEffect` の依存配列は、この「参照の同一性」をチェックします。`useRef` オブジェクト自体は変わらないので、依存配列に入れても `useEffect` が再実行されるトリガーにはなりにくい、というわけです。
—
じゃあ、どんな時に `useRef` を使うの?
ここまでで、「`useRef` を依存配列に入れるのは基本的にはナシ」ということが分かったかと思います。では、一体どんな時に `useRef` が活躍するのか、現場でよく使うシーンをいくつかご紹介しましょう。
現場でよく使う3つのシーン
1. DOM要素に直接アクセスしたい時
- 例えば、ユーザーがページを開いた瞬間に、特定の入力欄(``)にカーソルを合わせたい(フォーカスしたい)時。
- 動画の再生・停止をJavaScriptから制御したい時。
- スクロール位置を操作したい時。
これらはReactの仮想DOM(画面の設計図)を介さずに、ブラウザが持っている実際のHTML要素(DOM)に直接働きかける必要があるため、`useRef` が大活躍します。
2. 再レンダリングされても値を保持したいが、その値の変化で画面を更新する必要がない時
- タイマー(`setTimeout` や `setInterval`)のIDを保持しておき、後でタイマーを解除したい時。
- 特定の処理が実行中かどうかを示すフラグ(ただし、画面表示に影響しない場合)。
- イベントリスナーを登録する際に、最新のコールバック関数を保持したい時(これは少し応用ですが、非常に強力です)。
3. 直前のState値を保持したい時
- 現在のStateと「一つ前のState」を比較したい、といったケースですね。`useRef` を使うと、コンポーネントが再レンダリングされても、前の値を「忘れん坊ノート」に残しておくことができます。
—
実際のコードで見てみよう!`useRef` を依存配列に「入れない」ケース
それでは、実際にコードで見てみましょう。最もよく使う「DOM要素へのアクセス」の例です。
ユーザーがボタンをクリックすると、入力欄に自動でフォーカスが当たるシンプルなコンポーネントを考えてみましょう。
import React, { useRef, useEffect } from ‘react’;
function FocusInputExample() {
// 忘れん坊ノート(useRef)を用意します。
// ここにinput要素への参照を保管します。
const inputRef = useRef(null);
// useEffectを使って、コンポーネントが初めて表示された時に実行したい処理を書きます。
useEffect(() => {
// inputRef.current が存在すれば(つまり、input要素が画面に表示されていれば)
if (inputRef.current) {
// そのinput要素にフォーカスを当てます
inputRef.current.focus();
}
// 依存配列が空なので、この処理はコンポーネントが最初に表示された時だけ実行されます。
// inputRef 自体は、コンポーネントのライフサイクルを通じて変わらないため、
// 依存配列に含める必要はありません。
}, []); // 依存配列に inputRef は含めません!
const handleClick = () => {
// ボタンがクリックされた時にもフォーカスを当てたい場合
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
- はじめに:`useRef` と `useEffect`、その疑問にズバリ答えます!
- まずは基本のおさらい:`useRef` は「忘れん坊ノート」
- `useRef` の役割:画面に影響せず値を保持
- ここがポイント:値が変わっても再レンダリングしない!
- おさらいその2:`useEffect` は「監視役の番犬」
- 依存配列の役割:何を監視するのか?
- いよいよ本題!`useRef` を依存配列に入れるべきか、否か?
- 結論:基本的には「NO」です!
- その理由:「引き出し自体」は変わらないから
- もう少し深く:`useRef` オブジェクトは「常に同じ場所にある」
- じゃあ、どんな時に `useRef` を使うの?
- 現場でよく使う3つのシーン
- 実際のコードで見てみよう!`useRef` を依存配列に「入れない」ケース
- 入力欄に自動でフォーカスを当てる例
入力欄に自動でフォーカスを当てる例
↑ この入力欄に、最初にページが表示された時と、ボタンを押した時にカーソルが自動で当たります。
`inputRef`自体はコンポーネントのライフサイクルを通じて変わらないため、`useEffect`の依存配列に入れる必要はありません。
);
}
export default FocusInputExample;
このコードでは、`inputRef` を `useEffect` の依存配列に含めていませんよね?これは、`inputRef` という「箱」自体はコンポーネントが再レンダリングされても変わらないため、監視する必要がないからです。`useEffect` はコンポーネントが初めて表示された時に一度だけ実行され、`inputRef.current` にアクセスしてフォーカスを当てます。
—
もし間違えて `useRef` を依存配列に入れたらどうなるの?
「じゃあ、もし間違えて `useRef` を依存配列に入れちゃったら、どうなるの?」
これも良い質問ですね!多くの場合、期待通りの動作はしません。
例えば、先ほどのコードで依存配列を `[inputRef]` としてみたとしましょう。
`useEffect(() => { / … / }, [inputRef]);`
結果として、この `useEffect` はコンポーネントが最初にマウントされた時(画面に表示された時)にしか実行されません。なぜなら、`inputRef` という変数に格納されている `useRef` オブジェクト自体は、コンポーネントが再レンダリングされても、常に同じものを指しているからです。`useEffect` の「監視役の番犬」は、「何も変わってないな」と感じて、それ以上吠えることはありません。
つまり、`useRef.current` の値が変わったとしても、`useEffect` はそれを検知して再実行されることはない、ということです。これは、`useEffect` の依存配列の意図とは異なる使い方であり、混乱の元にもなりかねません。
—
まとめ:`useRef` と `useEffect` の賢い付き合い方
皆さん、今日の解説はいかがでしたか?「なるほど!」と膝を打っていただけたなら、私も嬉しいです。
`useRef` と `useEffect` は、Reactで高度なアプリケーションを構築する上で欠かせない強力なフックです。それぞれの役割をしっかり理解して、適切に使いこなすことが、バグの少ない、そしてパフォーマンスの良いコードを書くための秘訣です。
今日のポイントをもう一度おさらいしましょう。
- `useRef` は「忘れん坊ノート」:画面に影響を与えずに、コンポーネントの再レンダリングを越えて値を保持します。`.current` の値を変更しても、コンポーネントは再レンダリングされません。
- `useEffect` は「監視役の番犬」:依存配列に指定されたものが「変わった」時にだけ、中の処理を実行します。
- `useRef` を `useEffect` の依存配列に含めるべきか?:基本的には「NO」です。 なぜなら、`useRef` が返すオブジェクト自体は、コンポーネントのライフサイクルを通じて「変わらない」からです。監視しても変化がないため、`useEffect` が再実行されるトリガーにはなりません。
現場でコードを書いていると、つい「念のため」と色々依存配列に入れがちですが、本当に必要なものだけを入れるように意識することが大切です。
これで、あなたもReactの副作用の制御、特に `useRef` と `useEffect` の関係について、一歩プロに近づきましたね!
自信を持って、素晴らしいフロントエンドの世界を切り開いていってください!何か困ったことがあれば、いつでもまたこのブログに戻ってきてください。私も皆さんの挑戦を応援していますよ!

コメント