React

基本フック

React

useState・useEffect・useRef

useState

状態管理の基本

useState.tsx tsx
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

副作用・データフェッチ・クリーンアップ

useEffect.tsx tsx
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参照・ミュータブルな値の保持

useRef.tsx tsx
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;
}