useEffectによる非同期処理
Reactコンポーネントでデータ取得をしたい。そんなときに欠かせないのがuseEffectです。発火のタイミング、クリーンアップ、依存配列などの基本を、実例で身につけましょう(簡単にできます)。
この記事で学べること
- useEffectの基本と依存配列の意味
- データ取得のベストプラクティス(AbortControllerでのキャンセル)
- ローディング/エラー表示のパターン
- ありがちな落とし穴(無限ループなど)
useEffectの基本
useEffectは「レンダーのあと」に実行される副作用(データ取得や購読など)を記述するためのフックです。
import { useEffect, useState } from "react";
function UserName({ id }: { id: number }) {
const [name, setName] = useState<string>("");
useEffect(() => {
document.title = `User ${id}`; // レンダー後に副作用
}, [id]); // idが変わるたびに実行
return <h1>{name || "loading..."}</h1>;
}
Note: 依存配列(
[])が空だと、マウント時に1回だけ実行されます。
データ取得(非同期)を正しく書く
useEffect内でasync関数を直接渡すのではなく、中で宣言して呼び出します(細かいですが大事です)。
import { useEffect, useState } from "react";
type User = { id: number; name: string };
export function UserCard({ id }: { id: number }) {
const [user, setUser] = useState<User | null>(null);
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
const controller = new AbortController();
const { signal } = controller;
const load = async () => {
try {
setLoading(true);
setError("");
const res = await fetch(
`https://jsonplaceholder.typicode.com/users/${id}`,
{ signal },
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data: User = await res.json();
setUser(data);
} catch (e) {
if ((e as any)?.name === "AbortError") return; // アンマウント時の中断
setError((e as Error).message);
} finally {
setLoading(false);
}
};
load();
return () => controller.abort(); // クリーンアップで中断
}, [id]);
if (loading) return <p>読み込み中...</p>;
if (error) return <p style={{ color: "crimson" }}>エラー: {error}</p>;
if (!user) return null;
return (
<div>
<h2>{user.name}</h2>
<small>ID: {user.id}</small>
</div>
);
}
Note: ループやダブルフェッチを避けるため、依存配列に
userやloadingを安易に入れないようにしましょう。必要最小限にするのがコツです。
無限ループを避けるコツ
- 依存配列には「外から与えられる値」や「関数の安定化済み参照(useCallbackなど)」のみを入れる
- データを
setStateした結果に依存して再度fetchしないようにする - オブジェクト/配列リテラルは毎回新しい参照になるので注意(
useMemoで安定化)
型安全に書く(ざっくり)
TypeScriptでは、受け取るJSONの型を定義しておくと安心です。
type Todo = {
id: number;
title: string;
completed: boolean;
};
JSONをTodoにパースして使うだけで、プロパティのタイプミスに気づけます(助かりますよね)。
やってみよう!
- 上の
UserCardをコピーして、idを切り替えるボタンを用意 - 切り替え時に前のリクエストがキャンセルされることを確認(Networkタブで
(canceled)が出るはず) https://httpstat.us/404にリクエストしてエラー表示をテスト
ポイント(まとめ)
useEffectは「レンダー後の副作用」を書く場所- 非同期は
AbortControllerでキャンセル対応を入れる - 依存配列を最小化して無限ループを回避
- ローディング/エラー/成功の3状態をUIで明示
参考リンク
- React Docs: useEffect https://react.dev/reference/react/useEffect
- MDN: AbortController https://developer.mozilla.org/ja/docs/Web/API/AbortController