以下の記事の続きです。
[備忘録]UseEffectについて理解する①
UseEffectとは
前回の記事で、なんでuseEffect
が必要なのか理解できてきました。
また、レンダリング後にやりたい副作用処理を記述できることを理解しました。
本題のuseEffect
について、もう少し詳しく見ていきます。
基本的な実装は以下です。
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// 副作用処理
return () => {
// クリーンアップ処理
};
}, []); // 依存配列
}
依存配列について
依存配列に、副作用処理の中で使用している状態(state
)やプロパティ(props
)を指定することで、指定した値が変更されたタイミングで、副作用処理を実行できるようになります。(state
やprops
と副作用処理を同期できます。)
空の配列を指定した場合は、特に依存する値がないということで、初回レンダリング以降副作用処理が実行されません。
クリーンアップ関数について
コンポーネントが削除されるタイミングで実行され、副作用で行った処理の後始末を行います。
コンポーネントのライフサイクルとEffectの関係
Reactのコンポーネントのライフサイクルには以下の3つのフェーズがあります。
- マウント(Mount) → コンポーネントが初めて画面に表示される(コンポーネントが DOMに追加される瞬間にマウントされます。)
- 更新(Update)→ 状態やプロパティが変更される
- アンマウント(Unmount) → コンポーネントが画面から削除される
それに対して、Effect
は、props
、state
などのリアクティブな変数と、副作用との同期の開始と終了のさせ方を決める仕組みを提供しています。
この2つの関係をまとめると、以下のようなよく見る説明になります。
コンポーネントのライフサイクル | useEffectの書き方 | 説明(利用例) |
---|---|---|
マウント | useEffect(() => {...}, []) |
初回レンダリング後のみ実行(API取得、イベントリスナー追加などに使う) |
更新 | useEffect(() => {...}, [変数]) |
変数が変わるたびに実行(状態が更新されたときの処理) |
アンマウント | useEffect(() => { return () => {...}; }, []) |
コンポーネントが削除される前に実行(イベントリスナー削除、タイマー停止などに使う) |
useEffectが無かったら
関数コンポーネントでuseEffect
を使った場合と使わなかった場合で何がどう変わるかを見てみます。
以下の例では、Increment
ボタンをクリックしてcount
のstate
が更新されるたびに、Counter
コンポーネントの再レンダリングが走ります。
また、そのレンダリングの度にuser
を取得するためのフェッチが、無駄に走ります。
このように副作用処理をレンダリング中に記載すると、容易に意図しない挙動になります。
import { useState } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [count, setCount] = useState(0); // user とは関係ない state
// count が変わるたびに user取得の API リクエストが走ってしまう
fetch(`https://xxxxxxxxx/users/${userId}`)
.then((res) => res.json())
.then((data) => setUser(data));
return (
<div>
<p>{user ? user.name : "Loading..."}</p>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
}
今回の場合、user
の取得のためのフェッチを初回レンダリング後、または、UserId
が変わったときだけにしたいです。
そのような場合、useEffect
を利用することで、こういった副作用処理の問題を解決できます。
以下が修正したコードです。
import { useState, useEffect } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [count, setCount] = useState(0); // user に関係ない state
useEffect(() => {
fetch(`https://xxxxxxxxx/users/${userId}`)
.then((res) => res.json())
.then((data) => setUser(data));
}, [userId]); // 初回のレンダリングおよび userId が変わったときだけ実行
return (
<div>
<p>{user ? user.name : "Loading..."}</p>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
さいごに
useEffect
の超基本でした。
公式ドキュメントには、useEffectの利用方法のアンチパターンも載っていたので、さらにそれも読んで理解を深めたいと思います。
参考
https://ja.react.dev/learn/synchronizing-with-effects
https://ja.react.dev/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect