Reactチートシート

中級 | 15分 で読める | 2025.01.10

公式ドキュメント

概要

React は、コンポーネントを組み合わせて UI を作る JavaScript ライブラリです。このチートシートでは、Hooks、props、条件付きレンダリング、フォーム、カスタム Hooks、パフォーマンス最適化でよく使う構文を整理します。

最初に覚える最小セット

項目まず使う API / パターン
状態useState
副作用useEffect + クリーンアップ
DOM 参照useRef
propsTypeScript の interface
リストitems.map(...) + 安定した key
フォーム制御コンポーネント
ロジック再利用カスタム Hooks

React では state を直接変更せず、props から UI を導く流れを守りますuseEffect は必要な副作用に絞り、依存配列とクリーンアップを必ず確認します。

基本的なHooks

useState

// 基本
const [count, setCount] = useState(0);

// オブジェクト
const [user, setUser] = useState({ name: "", email: "" });
setUser(prev => ({ ...prev, name: "Alice" }));

// 関数で初期化(重い計算)
const [data, setData] = useState(() => expensiveComputation());

// 型指定
const [items, setItems] = useState<string[]>([]);

useEffect

// マウント時のみ
useEffect(() => {
 console.log("Mounted");
}, []);

// 依存配列の値が変わるたび
useEffect(() => {
 console.log("Count changed:", count);
}, [count]);

// クリーンアップ
useEffect(() => {
 const subscription = subscribe();
 return () => subscription.unsubscribe();
}, []);

// 非同期処理
useEffect(() => {
 const fetchData = async () => {
 const data = await fetch("/api/data");
 setData(await data.json());
 };
 fetchData();
}, []);

useRef

// DOM参照
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();

// 値の保持(再レンダリングしない)
const countRef = useRef(0);
countRef.current += 1;

// 前の値を保持
const prevValueRef = useRef(value);
useEffect(() => {
 prevValueRef.current = value;
}, [value]);

注意: useEffectの依存配列を空([])にするとマウント時のみ実行。依存配列を忘れると毎レンダリングで実行されるので注意しましょう。

useMemo / useCallback

// 計算結果のメモ化
const expensiveValue = useMemo(() => {
 return items.filter(item => item.active).map(item => item.value);
}, [items]);

// 関数のメモ化
const handleClick = useCallback((id: string) => {
 setSelectedId(id);
}, []);

// 子コンポーネントへの関数渡し
<ChildComponent onClick={handleClick} />

ポイント: useMemoは計算結果のメモ化、useCallbackは関数のメモ化。子コンポーネントへ関数を渡す場合はuseCallbackで不要な再レンダリングを防ぎます

useContext

// コンテキスト作成
const ThemeContext = createContext<Theme>("light");

// プロバイダー
<ThemeContext.Provider value="dark">
 <App />
</ThemeContext.Provider>

// 使用
const theme = useContext(ThemeContext);

useReducer

type State = { count: number };
type Action = { type: "increment" } | { type: "decrement" } | { type: "reset" };

function reducer(state: State, action: Action): State {
 switch (action.type) {
 case "increment":
 return { count: state.count + 1 };
 case "decrement":
 return { count: state.count - 1 };
 case "reset":
 return { count: 0 };
 }
}

const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: "increment" });

コンポーネントパターン

Props

// 基本
interface ButtonProps {
 label: string;
 onClick: () => void;
 disabled?: boolean;
}

function Button({ label, onClick, disabled = false }: ButtonProps) {
 return (
 <button onClick={onClick} disabled={disabled}>
 {label}
 </button>
 );
}

// children
interface CardProps {
 children: React.ReactNode;
 title?: string;
}

function Card({ children, title }: CardProps) {
 return (
 <div className="card">
 {title && <h2>{title}</h2>}
 {children}
 </div>
 );
}

条件付きレンダリング

パターンコード
&&演算子{isLoggedIn && <UserMenu />}
三項演算子{isLoading ? <Spinner /> : <Content />}
早期リターンif (isLoading) return <Spinner />;
if (error) return <Error message={error} />;
return <Content data={data} />;

リスト

パターンコード
map{items.map(item => (<ListItem key={item.id} item={item} />))}
インデックスをキー(非推奨){items.map((item, index) => (<ListItem key={index} item={item} />))}
フラグメント{items.map(item => (<Fragment key={item.id}><dt>{item.term}</dt><dd>{item.description}</dd></Fragment>))}

イベントハンドリング

イベントコード
クリック<button onClick={(e) => handleClick(e)}>Click</button>
フォーム<form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
入力<input value={value} onChange={(e) => setValue(e.target.value)} />
キーボード<input onKeyDown={(e) => { if (e.key === 'Enter') handleSubmit(); }} />

実践メモ: リストのレンダリングではkeyに一意なIDを使いましょう。インデックスをkeyに使うと、要素の追加・削除で不要な再レンダリングが発生します

フォーム

制御コンポーネント

function Form() {
 const [formData, setFormData] = useState({
 name: "",
 email: "",
 });

 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
 const { name, value } = e.target;
 setFormData(prev => ({ ...prev, [name]: value }));
 };

 const handleSubmit = (e: React.FormEvent) => {
 e.preventDefault();
 console.log(formData);
 };

 return (
 <form onSubmit={handleSubmit}>
 <input
 name="name"
 value={formData.name}
 onChange={handleChange}
 />
 <input
 name="email"
 type="email"
 value={formData.email}
 onChange={handleChange}
 />
 <button type="submit">送信</button>
 </form>
 );
}

React Hook Form

import { useForm } from "react-hook-form";

interface FormData {
 name: string;
 email: string;
}

function Form() {
 const { register, handleSubmit, formState: { errors } } = useForm<FormData>();

 const onSubmit = (data: FormData) => {
 console.log(data);
 };

 return (
 <form onSubmit={handleSubmit(onSubmit)}>
 <input {...register("name", { required: "名前は必須です" })} />
 {errors.name && <span>{errors.name.message}</span>}

 <input {...register("email", {
 required: "メールは必須です",
 pattern: {
 value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
 message: "無効なメールアドレスです"
 }
 })} />
 {errors.email && <span>{errors.email.message}</span>}

 <button type="submit">送信</button>
 </form>
 );
}

注意: オブジェクトのstate更新はスプレッド構文でイミュータブルに。setUser(prev => ({...prev, name: "Alice"}))のように、直接変更(ミューテーション)は避けましょう。

カスタムHooks

// フェッチ
function useFetch<T>(url: string) {
 const [data, setData] = useState<T | null>(null);
 const [loading, setLoading] = useState(true);
 const [error, setError] = useState<Error | null>(null);

 useEffect(() => {
 fetch(url)
 .then(res => res.json())
 .then(setData)
 .catch(setError)
 .finally(() => setLoading(false));
 }, [url]);

 return { data, loading, error };
}

// ローカルストレージ
function useLocalStorage<T>(key: string, initialValue: T) {
 const [value, setValue] = useState<T>(() => {
 const stored = localStorage.getItem(key);
 return stored ? JSON.parse(stored) : initialValue;
 });

 useEffect(() => {
 localStorage.setItem(key, JSON.stringify(value));
 }, [key, value]);

 return [value, setValue] as const;
}

// デバウンス
function useDebounce<T>(value: T, delay: number): T {
 const [debouncedValue, setDebouncedValue] = useState(value);

 useEffect(() => {
 const timer = setTimeout(() => setDebouncedValue(value), delay);
 return () => clearTimeout(timer);
 }, [value, delay]);

 return debouncedValue;
}

ポイント: ロジックの再利用はカスタムHooksで実現。useで始まる関数名にして、useStateやuseEffectを中で使います。

パフォーマンス最適化

// React.memo
const MemoizedComponent = React.memo(function Component({ value }: Props) {
 return <div>{value}</div>;
});

// カスタム比較関数
const MemoizedComponent = React.memo(Component, (prevProps, nextProps) => {
 return prevProps.id === nextProps.id;
});

// lazy loading
const LazyComponent = React.lazy(() => import("./HeavyComponent"));

<Suspense fallback={<Loading />}>
 <LazyComponent />
</Suspense>

実践メモ: React.lazyとSuspenseでコード分割。初期バンドルサイズを削減し、表示速度を改善できます。

よく使うパターン

パターンコード
ローディング状態{loading && <Spinner />}
{error && <ErrorMessage error={error} />}
{data && <Content data={data} />}
空状態{items.length === 0 ? (<EmptyState message="アイテムがありません" />) : (<ItemList items={items} />)}
条件付きクラス<div className={`card ${isActive ? "active" : ""}`} />
<div className={clsx("card", { active: isActive })} />

まとめ

React は、useStateuseEffect、props、リストの key、フォーム状態、カスタム Hooks を押さえると多くの UI を組み立てられます。最適化は最初から乱用せず、再レンダリングやバンドルサイズが問題になった箇所で memouseMemouseCallbacklazy を検討します。

Hooks のルールや新しい API は React のバージョンで変わる場合があります。詳細は公式リファレンスを確認してください。

参考リソース

関連記事

← 一覧に戻る
PR
PR
PR
PR