基本的な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
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();
const countRef = useRef(0);
countRef.current += 1;
const prevValueRef = useRef(value);
useEffect(() => {
prevValueRef.current = value;
}, [value]);
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} />
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>
);
}
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} />;
リスト
{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();
}}
/>
フォーム
制御コンポーネント
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>
);
}
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>
);
}
カスタム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;
}
パフォーマンス最適化
const MemoizedComponent = React.memo(function Component({ value }: Props) {
return <div>{value}</div>;
});
const MemoizedComponent = React.memo(Component, (prevProps, nextProps) => {
return prevProps.id === nextProps.id;
});
const LazyComponent = React.lazy(() => import("./HeavyComponent"));
<Suspense fallback={<Loading />}>
<LazyComponent />
</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 })} />
関連記事
← 一覧に戻る