ReactのuseState・useRef・constの違い:再レンダリングと値の保持を理解する
はじめに
Reactコンポーネントの中で値を扱うとき、useState・useRef・const のどれを使えばいいか迷うことがあります。それぞれ「値の保持」と「再レンダリング」の動きが異なります。
| 値の保持 | 変更時に再レンダリング | |
|---|---|---|
const(通常変数) |
✗(レンダリングごとに初期化) | — |
useState |
✅ | ✅ |
useRef |
✅ | ✗ |
この違いを理解することで、どれを使うべきか判断できるようになります。
const(通常変数):レンダリングごとに初期化される
コンポーネント内で const で宣言した変数は、レンダリングのたびに初期値に戻ります。
function Counter() { let count = 0; // レンダリングのたびに 0 に戻る const handleClick = () => { count += 1; console.log(count); // クリックするたびに 1 が表示される(累積しない) }; return <button onClick={handleClick}>{count}</button>; }
count を増やしても画面は変わりません。変更が再レンダリングを引き起こさないため、ボタンのラベルは常に 0 のままです。
使いどころ:レンダリング内で一時的に使う計算値やスタイル定義など、保持する必要がない値に使います。
function UserCard({ user }) { const displayName = `${user.lastName} ${user.firstName}`; // 毎回計算してよい const isAdmin = user.role === 'admin'; return ( <div> <p>{displayName}</p> {isAdmin && <span>管理者</span>} </div> ); }
useState:変更すると再レンダリングが走る
useState は値をレンダリング間で保持し、setState 関数を呼ぶと再レンダリングが走ります。UIに表示する値はこれを使います。
import { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); // 呼ぶと再レンダリングが走り、画面が更新される }; return <button onClick={handleClick}>{count}</button>; }
直接変更しても再レンダリングは走らない
// NG:直接変更しても画面は変わらない const handleClick = () => { count = count + 1; // これは動かない }; // OK:setState経由で変更する const handleClick = () => { setCount(count + 1); };
よく使うパターン
フォームの入力値
function Form() { const [name, setName] = useState(''); return ( <input value={name} onChange={e => setName(e.target.value)} /> ); }
表示・非表示の切り替え
function Modal() { const [isOpen, setIsOpen] = useState(false); return ( <> <button onClick={() => setIsOpen(true)}>開く</button> {isOpen && ( <div> <p>モーダルの内容</p> <button onClick={() => setIsOpen(false)}>閉じる</button> </div> )} </> ); }
オブジェクトの状態管理
const [user, setUser] = useState({ name: '', email: '' }); // 一部だけ更新する場合はスプレッド構文で既存の値を保持する setUser(prev => ({ ...prev, name: '田中太郎' }));
useRef:変更しても再レンダリングが走らない
useRef は値をレンダリング間で保持しますが、.current を変更しても再レンダリングは走りません。
import { useRef } from 'react'; function Counter() { const countRef = useRef(0); const handleClick = () => { countRef.current += 1; console.log(countRef.current); // 値は累積するがUIは変わらない }; return <button onClick={handleClick}>{countRef.current}</button>; // ボタンのラベルは常に 0 のまま(再レンダリングされないため) }
よく使うパターン
DOMに直接アクセスする
useRef の最も典型的な使い方がDOMへのアクセスです。
import { useRef } from 'react'; function SearchInput() { const inputRef = useRef<HTMLInputElement>(null); const handleFocus = () => { inputRef.current?.focus(); // 入力フィールドにフォーカスを当てる }; return ( <> <input ref={inputRef} type="text" /> <button onClick={handleFocus}>フォーカス</button> </> ); }
タイマーIDの保持
import { useRef } from 'react'; function Timer() { const timerRef = useRef<number | null>(null); const start = () => { timerRef.current = window.setInterval(() => { console.log('tick'); }, 1000); }; const stop = () => { if (timerRef.current !== null) { clearInterval(timerRef.current); } }; return ( <> <button onClick={start}>開始</button> <button onClick={stop}>停止</button> </> ); }
タイマーIDはUIに表示しないので useRef が適切です。useState にすると更新のたびに不要な再レンダリングが発生します。
前回の値を記録する
import { useState, useRef, useEffect } from 'react'; function PreviousValue() { const [count, setCount] = useState(0); const prevCountRef = useRef(0); useEffect(() => { prevCountRef.current = count; }); return ( <div> <p>現在: {count}</p> <p>前回: {prevCountRef.current}</p> <button onClick={() => setCount(c => c + 1)}>+1</button> </div> ); }
3つを比較する
実際に同じカウンターで動きの違いを確認します。
import { useState, useRef } from 'react'; function CompareAll() { let constCount = 0; // レンダリングごとに 0 に戻る const [stateCount, setStateCount] = useState(0); // 変更で再レンダリング const refCount = useRef(0); // 変更しても再レンダリングしない return ( <div> <div> <p>const: {constCount}</p> <button onClick={() => { constCount += 1; console.log(constCount); }}> +1(画面は変わらない) </button> </div> <div> <p>useState: {stateCount}</p> <button onClick={() => setStateCount(stateCount + 1)}> +1(画面が更新される) </button> </div> <div> <p>useRef: {refCount.current}</p> <button onClick={() => { refCount.current += 1; console.log(refCount.current); }}> +1(値は増えるが画面は変わらない) </button> </div> </div> ); }
使い分けの判断フロー
値をUIに表示する?
├── YES → useState
└── NO → 値をレンダリング間で保持する必要がある?
├── YES → useRef(タイマーID、DOM参照、前回値など)
└── NO → const(計算値、スタイル、一時変数など)
まとめ
| const | useState | useRef | |
|---|---|---|---|
| 値の保持 | ✗ | ✅ | ✅ |
| 変更で再レンダリング | — | ✅ | ✗ |
| 主な用途 | 一時的な計算値 | UIに表示する値 | DOM参照・タイマーID・前回値 |
- UIに表示する値 →
useState - UIに表示しないが保持したい値 →
useRef - 保持する必要がない一時的な値 →
const
APIフェッチやタイマーなど副作用の扱い方は「ReactのuseEffect入門:基本から気をつけることまで」を参照してください。