よくあるアンチパターン
よくあるアンチパターン
Section titled “よくあるアンチパターン”Reactでよくあるアンチパターンと、実際に事故った構造を詳しく解説します。
A. メモリリーク
Section titled “A. メモリリーク”実際に事故った構造
Section titled “実際に事故った構造”// ❌ アンチパターン: メモリリークfunction Component() { const [data, setData] = useState<any[]>([]);
useEffect(() => { // 問題: イベントリスナーが削除されない const handleResize = () => { setData(prev => [...prev, { timestamp: Date.now() }]); };
window.addEventListener('resize', handleResize); // 問題: クリーンアップ関数がない }, []);
return <div>Component</div>;}なぜ事故るか:
- イベントリスナーのリーク: イベントリスナーが削除されず、メモリリークが発生
- 参照の保持: クロージャーで参照が保持され、ガベージコレクションが動作しない
- ブラウザのクラッシュ: メモリ使用量が増加し、ブラウザがクラッシュ
設計レビューでの指摘文例:
【指摘】メモリリークが発生しています。【問題】イベントリスナーが削除されず、メモリリークが発生しています。【影響】メモリ使用量の増加、ブラウザのクラッシュ【推奨】useEffectのクリーンアップ関数でイベントリスナーを削除するB. 不要な再レンダリング
Section titled “B. 不要な再レンダリング”実際に事故った構造
Section titled “実際に事故った構造”// ❌ アンチパターン: 不要な再レンダリング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>;}なぜ事故るか:
- 再レンダリングの増加: 親コンポーネントが再レンダリングされるたびに、子コンポーネントも再レンダリング
- メモリ使用量の増加: 再レンダリングが増加し、メモリ使用量が増加
- パフォーマンスの低下: レンダリングパフォーマンスが低下
C. 大量のDOMノード
Section titled “C. 大量のDOMノード”実際に事故った構造
Section titled “実際に事故った構造”// ❌ アンチパターン: 大量の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> );}なぜ事故るか:
- DOMノード数の増加: 10,000個のDOMノードが作成される
- レンダリングパフォーマンスの低下: DOMノード数が増加すると、レンダリングパフォーマンスが低下
- メモリ使用量の増加: DOMノードはメモリを消費する
設計レビューでの指摘文例:
【指摘】DOMノード数が過剰です。【問題】10,000件のデータを一度にレンダリングしています。【影響】レンダリングパフォーマンスの低下、メモリ使用量の増加【推奨】仮想スクロールを使用してDOMノード数を制限するD. 無限ループ
Section titled “D. 無限ループ”【まさかり】無限ループの例について: 依存配列なしの例(
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>;}なぜ事故るか:
- 参照型の性質: オブジェクトや配列は参照型のため、
{ ...user }で新しいオブジェクトが作成される - 依存配列の検出:
userを依存配列に入れると、setUser({ ...user })で新しいオブジェクトが作成され、再びuseEffectが実行される - 無限ループ:
useEffectが無限に実行され、無限ループが発生 - メモリ使用量の増加: 無限ループにより、メモリ使用量が増加
- ブラウザのクラッシュ: 無限ループにより、ブラウザがクラッシュ
設計レビューでの指摘文例:
【指摘】無限ループが発生しています。【問題】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アプリケーションを構築できます。
重要な原則: 「正常に動く」よりも「異常時に安全に壊れる」ことを優先する。