
JSXの書き方:React開発の第一歩
JSX(JavaScript XML)はReactの核となる構文で、JavaScript内にHTMLのようなマークアップを直接記述できるようにします。これにより […]
Reactコンポーネントのライフサイクルと、関数型コンポーネントで副作用を扱うuseEffect
フックについて、初心者向けに徹底解説します。コンポーネントの「誕生」から「消滅」までの流れを理解することで、より効果的なReactプログラミングが可能になります。
Reactコンポーネントには、マウント(生成)→ 更新 → アンマウント(破棄)という3つの主要な段階があります。各段階で特定のコードを実行できるよう、Reactは「ライフサイクルメソッド」を提供しています。
componentDidMount
、componentDidUpdate
、componentWillUnmount
などの専用メソッドuseEffect
フックで全てのライフサイクル処理をカバーimport { useEffect } from 'react';
function ExampleComponent() {
useEffect(() => {
// コンポーネントのマウント時と更新時に実行される
console.log('コンポーネントがレンダリングされました');
return () => {
// クリーンアップ関数(後述)
console.log('コンポーネントがアンマウントされる前や再レンダリング前に実行');
};
}); // 依存配列なし(第2引数がない)
return <div>useEffectの例</div>;
}
useEffect(() => {
console.log('コンポーネントがマウントされました(初回のみ実行)');
return () => {
console.log('コンポーネントがアンマウントされます');
};
}, []); // 空の依存配列
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`countが変更されました: ${count}`);
}, [count]); // countが変更された時のみ実行
useEffect(() => {
const timer = setInterval(() => {
console.log('1秒ごとに実行');
}, 1000);
return () => {
clearInterval(timer); // アンマウント時にタイマーをクリア
console.log('タイマーがクリアされました');
};
}, []);
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true; // アンマウントフラグ
const fetchUserData = async () => {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
if (isMounted) {
setUser(data);
setLoading(false);
}
} catch (error) {
if (isMounted) {
console.error('データ取得エラー:', error);
setLoading(false);
}
}
};
fetchUserData();
return () => {
isMounted = false; // アンマウント時にフラグをfalseに
};
}, [userId]); // userIdが変更されたら再取得
if (loading) return <div>読み込み中...</div>;
if (!user) return <div>ユーザーが見つかりません</div>;
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
}
function ScrollLogger() {
useEffect(() => {
const handleScroll = () => {
console.log('スクロール位置:', window.scrollY);
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []); // 空の依存配列 = マウント/アンマウント時のみ
return <div style={{ height: '200vh' }}>スクロールしてコンソールを確認</div>;
}
function PageTitle({ title }) {
useEffect(() => {
document.title = title;
return () => {
document.title = 'デフォルトタイトル'; // オプション: 元に戻す
};
}, [title]); // titleが変更されるたびに更新
return <h1>{title}</h1>;
}
依存配列はuseEffectの第2引数で、どの値の変更を監視するかを指定します。
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// パターン1: countが変更された時のみ実行
useEffect(() => { /* ... */ }, [count]);
// パターン2: countまたはtextが変更された時実行
useEffect(() => { /* ... */ }, [count, text]);
// パターン3: 初回のみ実行
useEffect(() => { /* ... */ }, []);
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Count: ${count}`);
}, []); // countを依存配列に入れないと最新値が取得できない
useEffect(() => {
console.log('毎回レンダリング後に実行');
}); // 依存配列なし = 毎回実行 → パフォーマンス問題の原因に
const user = { id: 1, name: '山田太郎' };
useEffect(() => {
console.log(user);
}, [user]); // 毎回新しいオブジェクトが生成されるため、実質毎回実行
useEffectのreturnで返す関数は、次のタイミングで実行されます:
useEffect(() => {
// セットアップ処理
console.log('エフェクトが実行されました');
return () => {
// クリーンアップ処理
console.log('前回のエフェクトがクリーンアップされました');
};
}, [dependencies]);
useEffect(() => {
const timer = setTimeout(() => {
console.log('タイマー実行');
}, 1000);
return () => clearTimeout(timer);
}, []);
useEffect(() => {
const handleResize = () => {
console.log('ウィンドウサイズ:', window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
const response = await fetch('/api/data', {
signal: controller.signal
});
// データ処理
} catch (error) {
if (error.name !== 'AbortError') {
console.error('取得エラー:', error);
}
}
};
fetchData();
return () => {
controller.abort(); // リクエストをキャンセル
};
}, []);
1つのコンポーネント内で複数のuseEffectを使用することで、関心を分離できます。
function MultiEffectComponent({ userId }) {
// タイトル更新用
useEffect(() => {
document.title = `ユーザー: ${userId}`;
}, [userId]);
// データ取得用
const [user, setUser] = useState(null);
useEffect(() => {
const fetchUser = async () => {
const response = await fetch(`/api/users/${userId}`);
setUser(await response.json());
};
fetchUser();
}, [userId]);
// ウィンドウイベント用
useEffect(() => {
const handleScroll = () => {
console.log('スクロール中...');
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
// ...
}
const [count, setCount] = useState(0);
// 無限ループの例(state更新→再レンダリング→useEffect実行→state更新...)
useEffect(() => {
setCount(count + 1); // 依存配列にcountを含むと無限ループ
}, [count]);
// 解決策1: 依存配列からcountを除外(ただし非推奨)
useEffect(() => {
setCount(c => c + 1); // 関数型更新を使用
}, []); // 空の依存配列
// 解決策2: 条件付き更新
useEffect(() => {
if (count < 10) {
setCount(count + 1);
}
}, [count]);
const [user, setUser] = useState({ id: 1, name: '山田' });
// 非効率的(オブジェクト全体を依存にしている)
useEffect(() => {
console.log('ユーザー変更');
}, [user]);
// 改善版(必要なプロパティのみを依存に)
useEffect(() => {
console.log('ユーザーID変更');
}, [user.id]);
複雑なロジックはカスタムフックに抽出できます。
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return size;
}
// 使用例
function MyComponent() {
const { width, height } = useWindowSize();
return (
<div>
ウィンドウサイズ: {width} x {height}
</div>
);
}
useEffect(() => {
// エフェクトのロジック
}, [dependencies]);
// 依存関係が変更されたか確認
useEffect(() => {
console.log('依存関係が変更されました:', dependencies);
}, [dependencies]);
useEffect(() => {
console.log('エフェクトが実行されました');
return () => {
console.log('クリーンアップが実行されました');
};
}, [dependencies]);
useEffect(fn)
:毎回のレンダリング後useEffect(fn, [])
:マウント時のみuseEffect(fn, [dep])
:依存値変更時useEffectはReactの副作用管理の要です。適切に使用することで、外部システムとの同期やリソース管理を安全に行えます。初めは難しい概念かもしれませんが、実践を重ねることで自然と理解が深まります。次に学ぶフォーム処理と組み合わせると、さらに実践的なアプリケーション開発が可能になります。