Skip to content

よくあるアンチパターン

Reactでよくあるアンチパターンと、実際に事故った構造を詳しく解説します。

// ❌ アンチパターン: メモリリーク
function Component() {
const [data, setData] = useState<any[]>([]);
useEffect(() => {
// 問題: イベントリスナーが削除されない
const handleResize = () => {
setData(prev => [...prev, { timestamp: Date.now() }]);
};
window.addEventListener('resize', handleResize);
// 問題: クリーンアップ関数がない
}, []);
return <div>Component</div>;
}

なぜ事故るか:

  1. イベントリスナーのリーク: イベントリスナーが削除されず、メモリリークが発生
  2. 参照の保持: クロージャーで参照が保持され、ガベージコレクションが動作しない
  3. ブラウザのクラッシュ: メモリ使用量が増加し、ブラウザがクラッシュ

設計レビューでの指摘文例:

【指摘】メモリリークが発生しています。
【問題】イベントリスナーが削除されず、メモリリークが発生しています。
【影響】メモリ使用量の増加、ブラウザのクラッシュ
【推奨】useEffectのクリーンアップ関数でイベントリスナーを削除する
// ❌ アンチパターン: 不要な再レンダリング
function Component({ items }: { items: Item[] }) {
// 問題: 毎回新しい配列が作成される
const filteredItems = items.filter(item => item.active);
// 問題: 毎回新しい関数が作成される
const handleClick = (id: number) => {
console.log('Clicked:', id);
};
return (
<div>
{filteredItems.map(item => (
<ChildComponent key={item.id} item={item} onClick={handleClick} />
))}
</div>
);
}
// 問題: メモ化されていない
function ChildComponent({ item, onClick }: { item: Item; onClick: (id: number) => void }) {
return <div onClick={() => onClick(item.id)}>{item.text}</div>;
}

なぜ事故るか:

  1. 再レンダリングの増加: 親コンポーネントが再レンダリングされるたびに、子コンポーネントも再レンダリング
  2. メモリ使用量の増加: 再レンダリングが増加し、メモリ使用量が増加
  3. パフォーマンスの低下: レンダリングパフォーマンスが低下
// ❌ アンチパターン: 大量のDOMノード
function Component() {
const [items, setItems] = useState<Item[]>([]);
useEffect(() => {
// 問題: 10,000件のデータを一度に取得
fetch('/api/items')
.then(res => res.json())
.then(json => setItems(json));
}, []);
return (
<div>
{items.map(item => (
<div key={item.id}>{item.text}</div>
))}
</div>
);
}

なぜ事故るか:

  1. DOMノード数の増加: 10,000個のDOMノードが作成される
  2. レンダリングパフォーマンスの低下: DOMノード数が増加すると、レンダリングパフォーマンスが低下
  3. メモリ使用量の増加: DOMノードはメモリを消費する

設計レビューでの指摘文例:

【指摘】DOMノード数が過剰です。
【問題】10,000件のデータを一度にレンダリングしています。
【影響】レンダリングパフォーマンスの低下、メモリ使用量の増加
【推奨】仮想スクロールを使用してDOMノード数を制限する

【まさかり】無限ループの例について: 依存配列なしの例(useEffect(() => { setCount(count + 1); }))は、現代のエディタ(ESLint)が即座に赤波線を引いて教えてくれます。本当に現場で事故る無限ループは、もっと**「見えにくい」**ものです。

実際に事故った構造(参照型の性質による無限ループ)

Section titled “実際に事故った構造(参照型の性質による無限ループ)”
// ❌ アンチパターン: 参照型の性質による無限ループ
function Component() {
const [user, setUser] = useState<User>({ id: 1, name: 'John' });
const [data, setData] = useState<Data | null>(null);
useEffect(() => {
// 問題: userオブジェクトを更新している
setUser({ ...user, lastAccess: Date.now() });
}, [user]); // 問題: userを依存配列に入れている
useEffect(() => {
// 問題: dataオブジェクトを更新し、それを依存配列に入れている
fetch('/api/data')
.then(res => res.json())
.then(newData => {
setData(newData); // dataが更新される
});
}, [data]); // 問題: dataが変更されるたびに再実行され、無限ループが発生
return <div>{user.name}</div>;
}

なぜ事故るか:

  1. 参照型の性質: オブジェクトや配列は参照型のため、{ ...user }で新しいオブジェクトが作成される
  2. 依存配列の検出: userを依存配列に入れると、setUser({ ...user })で新しいオブジェクトが作成され、再びuseEffectが実行される
  3. 無限ループ: useEffectが無限に実行され、無限ループが発生
  4. メモリ使用量の増加: 無限ループにより、メモリ使用量が増加
  5. ブラウザのクラッシュ: 無限ループにより、ブラウザがクラッシュ

設計レビューでの指摘文例:

【指摘】無限ループが発生しています。
【問題】useEffectの中でオブジェクトをsetStateし、そのオブジェクトを別のuseEffectの依存配列に入れている。
【影響】無限ループ、メモリ使用量の増加、ブラウザのクラッシュ
【推奨】依存配列にはプリミティブ値(文字列、数値など)を入れる、または依存配列から除外する

✅ 解決策: 依存配列を適切に設定する

function Component() {
const [user, setUser] = useState<User>({ id: 1, name: 'John' });
const [data, setData] = useState<Data | null>(null);
// 解決: userオブジェクト全体ではなく、必要な値だけを依存配列に入れる
useEffect(() => {
setUser(prev => ({ ...prev, lastAccess: Date.now() }));
}, []); // 解決: 依存配列から除外する、または関数型の更新を使用
// 解決: dataを依存配列から除外する
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(newData => {
setData(newData);
});
}, []); // 解決: 依存配列から除外する
return <div>{user.name}</div>;
}

よくあるアンチパターンのポイント:

  • A. メモリリーク: イベントリスナーが削除されない → ブラウザのクラッシュ
  • B. 不要な再レンダリング: メモ化されていない → パフォーマンスの低下
  • C. 大量のDOMノード: 大量のデータを一度にレンダリング → レンダリングパフォーマンスの低下
  • D. 無限ループ: useEffectの依存配列が適切に設定されていない → ブラウザのクラッシュ

これらのアンチパターンを避けることで、安全で信頼性の高いReactアプリケーションを構築できます。

重要な原則: 「正常に動く」よりも「異常時に安全に壊れる」ことを優先する。