Reactを学び始めると、誰もがuseState
とuseRef
という二つのフックの壁にぶつかります。「どっちも値を保持するんでしょ?何が違うの?」そんな素朴な疑問から、この記事はスタートします。私が抱いた疑問を道しるべに、二つのフックの本質を解き明かしていきましょう。
---
そもそもReactとは?:「宣言的」なUIライブラリ
フックの話に入る前に、Reactがどのような思想で作られているかを知っておくことが、遠回りのようで一番の近道になります。
宣言的UI:Reactの核心
Reactは、ユーザーインターフェース(UI)を構築するための宣言的(Declarative)なライブラリです。
そもそも 宣言的(Declarative)とは何でしょうか。対立的な概念である、命令的(Imperative)と比較してみましょう。
-
命令的(Imperative):UIの変更方法を「どうやるか(How)」細かく指示する。「このボタンの色を赤に変えろ」「このテキストを『完了』に書き換えろ」など、手順を一つひとつ命令します。これは、従来のjQueryなどのアプローチです。
-
宣言的(Declarative):UIが「どうあるべきか(What)」だけを宣言する。「カウンターが10の時、このボタンは赤色であるべきだ」「データが読み込み中の時、UIは『ローディング中』と表示されるべきだ」のように、最終的なあるべき姿をコードで表現します。その状態に至るまでの具体的な手順は、Reactが裏側で全て良きに計らってくれます。
私たちは「こうあるべき」と宣言するだけでよく、面倒なDOM操作から解放される。これがReactの最大の魅力です。
コンポーネント:UIのレゴブロック
Reactでは、UIを「コンポーネント」という独立した小さな部品の集まりとして構築します。ボタン、入力フォーム、ヘッダーなど、UIのあらゆる部分を再利用可能なレゴブロックのように組み立てていくのです。
この記事で学ぶフックの位置づけ
-
`useState`: この「宣言的」なアプローチの核となるフックです。コンポーネントが「今、どういう状態であるべきか」をReactに宣言し、その状態が変わったことをReactに伝えるための最も重要な道具です。
-
`useRef`: 基本的には宣言的にUIを構築しますが、どうしても「このDOM要素にフォーカスを当てたい」のように命令的な操作が必要になることがあります。
useRef
は、そうした宣言的モデルから少しだけはみ出す必要がある場合の「緊急避難口」として機能します。
この大枠を理解すると、二つのフックの役割がより明確に見えてくるはずです。
---
useState:UIを動かす「発表係」📢
useState
は、Reactアプリケーションをインタラクティブにするための、最も基本的で重要なフックです。
疑問:「状態(State)」って、ただの変数と何が違うの?
一番大事な違いは、`useState`で管理する「状態」が更新されると、必ずUIの再レンダリング(再描画)が引き起こされることです。
useState
は、UIに直接影響を与える値を管理するための「公式発表係」だと考えてみましょう。カウンターの数字、フォームの入力内容、トグルのON/OFFなど、ユーザーの目に変化として映るべき値はすべて、この「状態」として管理します。
useState
を使うと、「現在の値」と、それを更新するための「セッター関数」のペアが手に入ります。
const [count, setCount] = useState(0);
このsetCount
という関数を呼び出すこと自体が、Reactに対する「値が変わったぞ!画面を更新しろ!」というトリガーになるのです。
疑問:「状態が変わると、何が起きるの?」 - Reactのレンダリングの仕組み
setCount(1)
のようなセッター関数を呼んでも、画面の一部分だけが魔法のように書き換わるわけではありません。実際には、Reactの内部で非常に効率的なプロセスが動いています。
-
Render(設計図の再作成):
setCount
が呼ばれると、Reactはコンポーネント関数を丸ごと再実行し、新しい状態に基づいたUIの設計図(仮想DOM)をメモリ上に作成します。 -
Reconciliation(差分の比較): Reactは、新しい設計図と古い設計図を比較し、変更があった箇所だけを正確に見つけ出します。
-
Commit(実際の工事): Reactは、見つけ出した差分だけを実際のブラウザ画面(本物のDOM)に適用します。これにより、最小限のコストで高速な画面更新が実現されます。
つまり、「変更箇所だけ更新される」というのは最終結果の話で、その前段階では「コンポーネント全体(の設計図)を再レンダリングしている」というのが重要なポイントです。コンポーネントを小さく分割することが、パフォーマンス向上に繋がる理由もここにあります。
疑問:「const [値, 関数] = useState()
」ってどういう意味?
これはJavaScriptの「配列の分割代入」という書き方です。
useState(初期値)
を呼び出すと、Reactは [現在の値, 更新用の関数]
という2つの要素が入った配列を返してくれます。この配列の1番目の要素にcount
、2番目の要素にsetCount
という名前を付けているのが、この一行の意味です。
重要なのは、更新用の関数(setCount
)は私たちが定義するのではなく、Reactから提供されるということです。私たちは、その関数を適切なタイミングで呼び出すことだけを考えれば良いのです。
---
useRef:再レンダリングしない「記憶の箱」🧰
useRef
も値を保持しますが、useState
とは決定的に違う性質を持っています。それは、値を更新しても、再レンダリングを一切引き起こさないことです。
疑問:「状態」と何が違うの?
useRef
は、UIの見た目とは無関係な値を、コンポーネントのライフサイクルを通じてこっそり覚えておくための「記憶の箱」や「個人的な道具箱」です。中身をいくらいじっても、UIに「変わりましたよ!」というお知らせはしません。
この「再レンダリングしない」という性質が、useRef
の存在意義そのものです。
疑問:どんな時に便利なの?
useRef
が輝くのは、主に次の2つのケースです。
-
DOM要素への直接アクセス: 特定の
<input>
にフォーカスを当てたり、<video>
を再生したり、要素のサイズを測定したりと、DOM要素そのものを直接操作したいときに使います。DOMへの参照を保持するのに、再レンダリングは不要です。 -
再レンダリング不要の値の保持:
setTimeout
のタイマーIDや、前回のレンダリング時のpropsの値など、覚えてはおきたいけれど、その値の変更がUIの変更に繋がらない値を管理するのに最適です。
「一般的なプログラミング言語における、普通の変数」の感覚に一番近いのがuseRef
です。
---
実践編:useStateとuseRefを協力させる高度なテクニック
この二つのフックは、敵対するものではなく、協力させることで真価を発揮します。
疑問:「計算は裏方、発表は主役」という役割分担はできる?
まさに、それが高度なReactプログラミングのテクニックです。「頻繁に発生する計算や状態管理はuseRef
で静かに行い、最終的な結果だけをuseState
でUIに発表する」ことで、パフォーマンスを最適化できます。
例えば、「箱の中をクリックしたときの相対座標を計算して表示する」機能を考えてみましょう。
import React, { useState, useRef, useCallback } from 'react';
function ClickArea() {
// ① 最終的に表示する「相対座標」を保持する (発表係 📢)
const [relativePos, setRelativePos] = useState({ x: 0, y: 0 });
// ② 座標計算の基準となる「箱」のDOM要素を掴んでおく (測定器 📏)
const containerRef = useRef(null);
const handleClick = useCallback((e) => {
// まず、useRefで掴んでおいた箱の情報を取得・測定する
const containerNode = containerRef.current;
if (!containerNode) return;
const rect = containerNode.getBoundingClientRect();
// 測定結果とイベント情報から、最終的な値を計算する
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 計算した最終結果だけを、useStateでUIに反映させる!
setRelativePos({ x: Math.round(x), y: Math.round(y) });
}, []);
return (
<div>
<div
ref={containerRef}
className="click-area"
onClick={handleClick}
>
<p>この箱の中の好きな場所をクリック!</p>
</div>
<div className="result-display">
<h3>クリックした相対座標:</h3>
<p>X: {relativePos.x}, Y: {relativePos.y}</p>
</div>
</div>
);
}
このコードでは、useRef
が「箱の位置を測る」という裏方の仕事を、useState
が「計算結果の座標を表示する」という主役の仕事を、見事に分担しています。
---
まとめ:どっちを使うかの判断基準
最後に、究極の判断基準を授けます。迷ったときは、自分にこう問いかけてみてください。
「この値が変わったとき、ユーザーは画面上で変化を確認する必要があるか?」
-
YES なら → `useState`
-
NO なら → `useRef`
このシンプルなルールが、あなたのReact開発の助けになることを願っています。