Next.js useState と useRef の違いは?最適な使い分け
React や Next.js を学び始めると、ほぼ必ず登場する
useState と useRef。
どちらも値を保持するためのフックですが、内部的な挙動が大きく異なります。
- 何が違うの?
- どっちを使うべき?
- useRef の方が速いって本当?
と疑問を感じる人も多いはずです。
本記事では、「初めて触る人でもスッと理解できる」ことを目指して、役割の違い・使い分け・具体例・パフォーマンスの観点までまとめて解説します。
🧩 useState と useRef の違い
まず結論から。
| 比較ポイント | useState | useRef |
|---|---|---|
| 値を変更すると再レンダーされる? | される | されない |
| 主な用途 | UI を変えたい時 | 内部処理・DOM 操作 |
| 値の性質 | 不変(変更で新レンダー) | ミュータブル(自由に書き換え可) |
| React が監視する? | する | しない |
▼ 一言でまとめると…
- UI に関係するなら useState
- UI に出さない内部値なら useRef
🟦 useState とは?(UI のための状態管理)
useState は「UIを更新したい時」に使うフックです。
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(prev => prev + 1); // ← ここで再レンダー
};
◆ 特徴
setStateを呼ぶとコンポーネントが再レンダーされる- React が値変更を検知してくれる
- 値は「その時点のレンダーに紐づく」ので、クロージャ問題の対処が必要なこともある
◆ こんな時に使う
- テキスト入力の値
- ボタン押下回数
- 表示すべきフラグ(モーダル ON/OFF)
- 選択中のタブ、ページ番号
✕ useState を使うべきでないケース
- UI に表示しない内部カウンタ
- API回数
- 一度だけ実行したかのフラグ
これらは useRef の方が適切です。
🟩 useRef とは?(再レンダーしない “箱”)
useRef は React コンポーネント内で
ミュータブルな値を保持するための「箱」 を提供します。
const countRef = useRef(0);
const handleClick = () => {
countRef.current += 1; // ← 再レンダーされない
console.log(countRef.current);
};
◆ 特徴
.currentに自由に代入できる- 値が変わっても再レンダーされない
- コンポーネントがアンマウントするまで永続
React はこの値を監視しないため
「内部用の値」「レンダーと関係ないもの」に最適。
◆ よく使う用途
- DOM 要素の参照(
<input ref={ref} />) - タイマー ID(
setTimeout,setInterval) - API 呼び出し回数のカウント
- 「連打防止」のフラグ
- 前回の値保持(スクロール位置など)
🔥 useRef の方が速いって本当?
✔ 一部のケースで YES
✕ でも常に速いわけではない。
ポイントはこれ:
useRef は「再レンダーが走らない」ため、結果的にパフォーマンスが良いことがある。
具体例を見ればわかります。
❌ NG: 内部値なのに useState を使ってしまう例
const [apiCallCount, setApiCallCount] = useState(0);
const fetchData = async () => {
setApiCallCount(c => c + 1); // 毎回 UI が再レンダーされてしまう
};
UI に表示しないのに再レンダーはムダです。
✔ OK: useRef を使うべき
const apiCallCountRef = useRef(0);
const fetchData = async () => {
apiCallCountRef.current++; // これで良い
};
無駄な再レンダーが発生しない → 結果的に高速。
🧭 “どちらを使う?” チェックリスト
▼ useState が適切
- 値の変化で UI を更新したい
- 値の変化を他のコンポーネントに伝えたい
- 入力フォーム / ボタン / フラグが関係する
▼ useRef が適切
- UI に表示しない値
- DOM 要素(input, video など)
- タイマー ID
- 初回実行したかどうかのフラグ
- 前回値の保持
- ボタン連打防止
🧪 実例:Next.js でよくある useRef の便利活用
(1) ボタン連打防止
const isSendingRef = useRef(false);
const handleSend = async () => {
if (isSendingRef.current) return;
isSendingRef.current = true;
await sendMessage();
isSendingRef.current = false;
};
再レンダーが不要なフラグはこれで十分。
(2) 前回レンダー時の値を保持
const prevValueRef = useRef<string>();
useEffect(() => {
if (prevValueRef.current !== undefined) {
console.log("前回値:", prevValueRef.current);
}
prevValueRef.current = value;
}, [value]);
(3) video / canvas の DOM 操作
const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (videoRef.current) {
videoRef.current.play();
}
}, []);
📝 まとめ
| 要点 | 結論 |
|---|---|
| UI を更新したい | useState |
| UI に出ない内部変数 | useRef |
| useRef の方が速い? | UI に関係ない処理では YES |
| DOM にアクセス | useRef |
| 再レンダーを抑えたい | useRef |
React/Next.js では、
「何が UI と関係していて、何が内部状態か?」
を見極めることで設計が大きく変わります。
📚 関連タグ
.htaccessは階層で引き継がれる?Apacheの挙動を徹底解説
SEO対策に必須!検索上位を狙うsitemap.xmlの完全ガイド
Google Search Consoleの「代替ページ(適切なcanonicalタグあり)」とは?原因と解決法を徹底解説!
Microsoft純正の新しいコンソールエディタ「edit」が復活!| edit.exe インストール方法
Googleサイト確認のTXTレコードをnslookupで確認する方法【SEO対策】
Googleサーチコンソールに反映されるための最低限のSEO構造とは
【Anker Soundcore Liberty 4】イヤーピース紛失!代替品はAmazonで購入
JavaScriptでタイムゾーン変換!UTCとJST(日本時間)の変換方法
git switchの使い方とgit checkoutとの違い
HTMLとJavaScriptモジュールでクラスを定義し、ボタンから呼び出す方法
JavaScriptでTensorFlow.jsを動的に読み込む方法|HTMLに直接書かずに機械学習を実行する
ffmpegでMOVファイルを逆再生する方法【音声付き対応】
Windows 11でタスクマネージャー以外からアプリを終了させる方法【PowerShell・コマンドプロンプト】
JavaScriptでPCの空き容量やメモリ量を取得できる?Chromeの制限と代替手法
PowerShellでNode.jsの最新バージョン一覧を確認する方法【Volta/Windows対応】
Next.jsでbasePathを/homepage2にしてS3へ静的デプロイする完全手順
NextAuthなし・APIルートなしで実装:Next.js + AWS Amplify だけでメール/パスワード認証を完結する方法
「Next.js pnpm build (ビルド)後の出力ファイルが見当たらない」トラブルシュート&静的デプロイ
AWS Amplify CLIでS3やZIPから手動デプロイ【実行コマンド付き】
Next.jsで複数サイズのPNGファビコンを指定する方法【layout.tsx対応】
Next.jsでのtypeとinterfaceの違いと使い分け方
Next.js useState と useRef の違いは?最適な使い分け
GraphQL接続の落とし穴:Amplify Hosting(amplifyapp.com) vs S3+Next.js(静的書き出し)比較