基本フック
React
useState・useEffect・useRef
useState
状態管理の基本
import { useState } from 'react';
// 基本
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [user, setUser] = useState<User | null>(null);
// 更新
setCount(5); // 直接指定
setCount(prev => prev + 1); // 関数型更新(前の値を使うとき)
// オブジェクト状態(イミュータブルに更新)
const [form, setForm] = useState({ name: '', email: '' });
setForm(prev => ({ ...prev, name: 'Alice' })); // 一部だけ更新
// 配列状態
const [items, setItems] = useState<string[]>([]);
setItems(prev => [...prev, 'new item']); // 追加
setItems(prev => prev.filter(i => i !== 'old')); // 削除
setItems(prev => prev.map(i => i === 'x' ? 'y' : i)); // 更新
// 遅延初期化(重い初期値の計算)
const [data, setData] = useState(() => {
const saved = localStorage.getItem('data');
return saved ? JSON.parse(saved) : [];
});useEffect
副作用・データフェッチ・クリーンアップ
import { useEffect, useState } from 'react';
// 基本パターン
useEffect(() => {
// 副作用処理
document.title = `Count: ${count}`;
}, [count]); // count が変わるたびに実行
useEffect(() => { /* マウント時のみ実行 */ }, []);
useEffect(() => { /* 毎レンダリング後に実行 */ });
// クリーンアップ
useEffect(() => {
const handler = () => console.log('resize');
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler); // クリーンアップ
}, []);
// データフェッチ(abort 対応)
function UserProfile({ userId }: { userId: number }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then(r => r.json())
.then(setUser)
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
return () => controller.abort(); // アンマウント時にキャンセル
}, [userId]);
return user ? <div>{user.name}</div> : <Spinner />;
}useRef
DOM参照・ミュータブルな値の保持
import { useRef, useEffect } from 'react';
// DOM参照
function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;
}
// ミュータブルな値(変更してもレンダリングしない)
function Timer() {
const [time, setTime] = useState(0);
const intervalRef = useRef<ReturnType<typeof setInterval>>();
const isRunningRef = useRef(false);
const start = () => {
if (isRunningRef.current) return;
isRunningRef.current = true;
intervalRef.current = setInterval(() => setTime(t => t + 1), 1000);
};
const stop = () => {
clearInterval(intervalRef.current);
isRunningRef.current = false;
};
useEffect(() => () => clearInterval(intervalRef.current), []);
return <div>{time}s <button onClick={start}>▶</button> <button onClick={stop}>⏹</button></div>;
}
// 前の値を保持
function usePrevious<T>(value: T) {
const ref = useRef<T>();
useEffect(() => { ref.current = value; });
return ref.current;
}