ReactのcomponentDidUpdateとuseEffectの違いを徹底解説!初心者でもわかるReactライフサイクルの基本
生徒
「先生、ReactのcomponentDidUpdateとuseEffectってどう違うんですか?」
先生
「いいところに気が付きましたね。どちらも“コンポーネントが更新されたとき”に動く処理を担当しますが、書き方と使い方が異なります。」
生徒
「なるほど!でも、最近はクラスコンポーネントより関数コンポーネントが多いって聞きますけど、それでも違いを知っておいた方がいいんですか?」
先生
「その通りです。古いコードや他人のプロジェクトではcomponentDidUpdateをよく見かけますし、今後の理解にもつながります。では、実際に比較してみましょう!」
1. Reactのライフサイクルとは?
Reactのライフサイクルとは、コンポーネント(画面の部品)が「生まれて」「更新されて」「消える」までの流れのことです。たとえば、ページが表示されたときや、ユーザーがボタンを押して内容が変わったときなどに、特定のタイミングで処理を実行できます。
クラスコンポーネントでは、このライフサイクルを細かく制御するために「ライフサイクルメソッド」という特別な関数を使います。そのひとつがcomponentDidUpdateです。
2. componentDidUpdateとは?
componentDidUpdateは、クラスコンポーネントで「更新後」に実行されるメソッドです。つまり、コンポーネントが最初に表示されたあと、状態(state)やプロパティ(props)が変更されたときに呼び出されます。
import React from "react";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log("カウントが更新されました:", this.state.count);
}
}
render() {
return (
<div>
<h1>カウント: {this.state.count}</h1>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
増やす
</button>
</div>
);
}
}
export default App;
componentDidUpdateでは、前回の状態と今の状態を比較して、特定の条件のときだけ処理を行うことができます。たとえば「特定の値が変わったときだけAPIを呼ぶ」といった使い方ができます。
3. useEffectとは?
次に、関数コンポーネントで使うのがuseEffectです。useEffectは「React Hooks(フック)」と呼ばれる仕組みのひとつで、関数コンポーネントでもライフサイクルのような処理を実現できます。
useEffectは、コンポーネントが表示されたあとや、特定の値が変化したときに実行されます。第2引数に「依存配列(へいぞんはいれつ)」というものを指定して、どの値が変わったときに実行するかを制御します。
import React, { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("カウントが更新されました:", count);
}, [count]);
return (
<div>
<h1>カウント: {count}</h1>
<button onClick={() => setCount(count + 1)}>増やす</button>
</div>
);
}
export default App;
[count]のように配列の中に値を入れると、その値が変化したときだけ実行されます。もし配列を空にすると、初回のみ実行され、これはcomponentDidMountに近い動作になります。
4. componentDidUpdateとuseEffectの違いを比較
両方とも「更新後の処理」に使われますが、実際の使い方や書き方にはいくつかの違いがあります。以下の表で整理してみましょう。
| 項目 | componentDidUpdate | useEffect |
|---|---|---|
| 使う場所 | クラスコンポーネント | 関数コンポーネント |
| 実行タイミング | 更新後(stateやpropsの変更時) | 依存配列の中の値が変わったとき |
| 条件分岐 | 前回の状態と比較して手動で制御 | 依存配列によって自動で制御 |
| 書き方の簡単さ | 少し複雑 | シンプルで分かりやすい |
| アンマウント処理 | componentWillUnmountを使用 |
useEffect内でクリーンアップ関数を返す |
React公式では、関数コンポーネントとuseEffectを使うことを推奨しています。理由は、コードが短く書けて、複数の処理を分けて書けるからです。
5. useEffectで複数の値を監視する例
依存配列には複数の値を入れることもできます。これによって、「どちらかの値が変わったときに処理を実行する」という動きも簡単に作れます。
import React, { useState, useEffect } from "react";
function App() {
const [name, setName] = useState("太郎");
const [age, setAge] = useState(20);
useEffect(() => {
console.log("名前または年齢が更新されました");
}, [name, age]);
return (
<div>
<h1>{name}({age}歳)</h1>
<button onClick={() => setName("花子")}>名前を変更</button>
<button onClick={() => setAge(age + 1)}>年齢を変更</button>
</div>
);
}
export default App;
このように、依存配列をうまく使うことで、componentDidUpdateで行っていた「状態の変化の監視」をより簡単に実現できます。
6. どちらを使うべき?
結論として、これからReactを学ぶならuseEffectを使うのがおすすめです。理由は、関数コンポーネントの方が軽量で読みやすく、将来のReactの開発でも標準的だからです。
ただし、既存のシステムでcomponentDidUpdateを使っているケースも多いため、違いを理解しておくことは非常に重要です。どちらの仕組みもReactの「更新の流れ(ライフサイクル)」を理解するうえで大切な知識になります。
まとめ
今回の記事では、React開発における重要な概念であるライフサイクル、特に「更新系」の処理を担うcomponentDidUpdateとuseEffectの違いについて詳しく解説してきました。Reactの進化とともに、クラスコンポーネントから関数コンポーネントへと主流が移り変わりましたが、その根本にある「状態の変化を検知して特定の副作用を実行する」という考え方は共通しています。
クラスコンポーネントにおけるcomponentDidUpdateは、直前のプロパティや状態(prevProps, prevState)と現在の値を開発者が明示的に比較することで処理を制御していました。一方で、モダンなReact開発の標準であるuseEffectは、依存配列(Dependency Array)という仕組みを利用することで、より宣言的かつ簡潔に副作用を記述できるようになっています。
エンジニアが押さえておくべきポイント
実務レベルでこれらを使い分ける、あるいは移行(リファクタリング)する際に意識すべき点は、コードの可読性とメンテナンス性です。useEffectは一つのコンポーネント内に複数記述できるため、ロジックごとに処理を分割できるという大きなメリットがあります。これにより、巨大なクラスメソッドの中に複雑な条件分岐(if文)が乱立するのを防ぐことができます。
実践的なコード例:複数の状態管理と副作用
最後に、これまでの学習の総仕上げとして、より実践に近いコードを確認してみましょう。複数の状態を監視し、特定の条件でログ出力やデータの保存をシミュレートするプログラムです。
import React, { useState, useEffect } from "react";
function UserProfile() {
const [userName, setUserName] = useState("ゲスト");
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [logCount, setLogCount] = useState(0);
// ログイン状態が変化したときだけ実行される処理
useEffect(() => {
if (isLoggedIn) {
console.log(`${userName}さんがログインしました。`);
} else {
console.log("ログアウトしました。");
}
}, [isLoggedIn]); // 依存配列にisLoggedInを指定
// ユーザー名が更新されたときにカウントを増やす処理
useEffect(() => {
setLogCount((prev) => prev + 1);
console.log(`名前が変更されました。現在の変更回数: ${logCount + 1}`);
}, [userName]);
return (
<div className="container mt-4">
<div className="card shadow-sm p-4">
<h2 className="h4 mb-3">ユーザー設定</h2>
<p>現在のユーザー: <strong>{userName}</strong></p>
<p>状態: {isLoggedIn ? "オンライン" : "オフライン"}</p>
<div className="d-flex gap-2 mt-3">
<button
className="btn btn-primary"
onClick={() => setUserName("React太郎")}
>
名前を更新
</button>
<button
className={`btn ${isLoggedIn ? "btn-danger" : "btn-success"}`}
onClick={() => setIsLoggedIn(!isLoggedIn)}
>
{isLoggedIn ? "ログアウト" : "ログイン"}
</button>
</div>
<div className="mt-3 text-muted small">
更新履歴: {logCount} 回
</div>
</div>
</div>
);
}
export default UserProfile;
このように、Reactのライフサイクルを正しく理解し、適切なHookを選択することは、バグの少ない洗練されたアプリケーション開発への第一歩となります。特にuseEffectの依存配列の扱いに慣れることで、不必要な再レンダリングを防ぎ、パフォーマンスの高いサイト制作が可能になります。
生徒
「先生、ありがとうございました!componentDidUpdateで頑張ってif文を書いていた処理が、useEffectの依存配列を使うとこんなにスッキリ書けるなんて驚きです。」
先生
「そうでしょう。依存配列のおかげで『どの値が変わった時に何をするか』が直感的にわかりますよね。ただ、配列に入れ忘れると古い値(クロージャの罠)を参照してしまうこともあるので、そこは注意が必要です。」
生徒
「クロージャの罠……。奥が深いですね。ちなみに、現場ではまだクラスコンポーネントを見かけることはありますか?」
先生
「ええ、数年前に作られた大規模なプロジェクトでは現役で動いていることも多いですよ。だからこそ、今日の比較表で学んだ知識が役に立ちます。新旧両方の書き方が分かれば、どんなプロジェクトにアサインされても怖くありませんね。」
生徒
「なるほど。歴史を知ることで今の便利さがより理解できました。これからは積極的にuseEffectを使いこなせるように練習してみます!」
先生
「その意気です!次はuseEffectでのクリーンアップ処理、つまりcomponentWillUnmountに相当する動きについても学んでいきましょう。これでライフサイクルのマスターにまた一歩近づけますよ。」