React Hooks について本ブログでもよく詳しい解説をしていますが、今回はその中でも特に重要な useEffect
を解説します。
useState
と同じぐらいよく使われる Hooks ですので、この機会にしっかりおさえておきましょう
ReactのuseEffectとは何か?
ReactのuseEffect
は、関数型コンポーネントで副作用を扱うためのHookです。
副作用とは、関数の実行によって発生する外部の変更のことを指します。
例えば、APIからデータを取得する処理や、DOMの変更などが副作用にあたります。
useEffect
は、コンポーネントがレンダリングされた後に実行されます。
これは、コンポーネントが画面に表示された後に何かを行いたいときに便利です。
例えば、以下のコードでは、コンポーネントがレンダリングされた後にこんにちは、世界!
をコンソールに出力します。
useEffect(() => {
console.log('こんにちは、世界!');
}, []);
この例だけではuseEffect
のメリットが分かりにくいかもしれません。
そこで、APIからデータを取得するより具体的な例を見てみましょう。
useEffect(() => {
fetch('https://api.example.com/items')
.then(res => res.json())
.then(
(result) => {
setItems(result);
},
(error) => {
setError(error);
}
)
}, []);
上記の例では、useEffect
を使用してAPIからデータを取得しています。
第2引数の空配列により、この副作用はコンポーネントがマウントされた時に一度だけ実行されます。
このように、useEffect
を使うことで、クラスコンポーネントのライフサイクルメソッドを関数コンポーネントで扱うことができます。
ライフサイクルメソッドとは、コンポーネントの生成から破棄までの間に発生するイベントのことです。
例えば、コンポーネントが生成された時に実行されるcomponentDidMount
や、コンポーネントが破棄される時に実行されるcomponentWillUnmount
などがあります。
useEffectの基本的な使い方
useEffect
は、コンポーネントの初回レンダリング後と、依存配列(後述)にある何かが変更された時に実行されます。
以下の例では、count
が変更されるたびに、その新しい値がコンソールに出力されます。
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`カウントは現在 ${count} です`);
}, [count]);
依存配列とは何か?
useEffect
の第二引数として「依存配列」を提供します。
この配列は、useEffect
が監視する値のリストです。
これらの値のうちの1つが変わるたびに、useEffect
内のコードが再度実行されます。
ただし、依存配列が空の場合、useEffect
はコンポーネントがマウントされた時に一度だけ実行されます。
そのため、画面が表示された時に一度だけ実行したい処理を記述する場合は、依存配列を空にすると良いでしょう。
useEffect(() => {
// 画面が表示された時に一度だけ実行したい処理
console.log('こんにちは、世界!');
}, []);
useEffectで非同期処理を行う
useEffect
は、データのフェッチなどの非同期操作を行うときによく使われます。
ただし、useEffect
のコールバック自体は同期的であるため、非同期関数を直接呼び出すことはできません。
そのため、通常は非同期関数を定義してからそれを呼び出します。
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
};
fetchData();
}, []);
クリーンアップ関数とは何か?
useEffect
の内部から関数を返すことで、副作用の「クリーンアップ」を行うことができます。
このクリーンアップは、次の副作用が開始する前またはコンポーネントがアンマウントされるときに実行されます。
以下のコードでは、タイマーを設定し、そのタイマーをクリーンアップするための関数を返しています。
useEffect(() => {
const timerID = setTimeout(() => {
console.log('1秒経過しました');
}, 1000);
return () => {
clearTimeout(timerID);
};
}, []);
上記の例では、タイマーをクリーンアップするためにclearTimeout
を使用しています。
これがないと、コンポーネントがアンマウントされた後もタイマーが動き続けてしまい、思わぬバグの原因になります。
他の例も見てみましょう。
例えば、APIからデータを取得する処理を行う際に、その処理が終わる前にユーザーがページを離れてしまった場合、APIへのリクエストをキャンセルするために使うことができます。
useEffect(()
=> {
const controller = new AbortController();
const signal = controller.signal;
fetch('https://api.example.com/items', { signal })
.then(res => res.json())
.then(
(result) => {
setItems(result);
},
(error) => {
setError(error);
}
)
return () => {
controller.abort();
};
}, []);
上記の例では、AbortController
を使用してAPIへのリクエストをキャンセルしています。
これにより、ユーザーがページを離れた後もAPIへのリクエストが続くことを防ぐことができます。
useEffectの注意点
ループや条件文内での使用を避ける
ReactのHookはトップレベルで呼び出す必要があります。
ループ、条件、入れ子になった関数の中でHookを呼び出すことは避けましょう。
例えば、以下のようなコードは動作しません。
function Counter() {
const [count, setCount] = useState(0);
if (count === 0) {
useEffect(() => {
console.log('カウントは現在 0 です');
});
}
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>増加</button>
</div>
);
}
上記の例では、count
が0の時にのみuseEffect
を呼び出そうとしていますが、React Hooks の仕様上、これは動作しません。
依存配列に関数やオブジェクトを含める際の注意
ReactのuseEffect
を使う際、依存配列に関数やオブジェクトを含めることがあります。しかし、これには注意が必要です。なぜなら、関数やオブジェクトは、コンポーネントが再レンダリングされるたびに「新しい参照」を持つからです。
「新しい参照」とは何かというと、JavaScriptでは関数やオブジェクトはそれぞれ一意の場所(参照)に保存されます。そして、コンポーネントが再レンダリングされると、その中で定義された関数やオブジェクトは新たな場所に保存され、それが「新しい参照」になります。
これが問題となるのは、useEffect
の依存配列に関数やオブジェクトを含めた場合です。依存配列に含まれる値が変更されると、useEffect
内のコードが再実行されます。しかし、関数やオブジェクトが新しい参照を持つたびに、それが「変更」とみなされ、useEffect
が再実行されてしまいます。
例えば、以下のコードを見てみましょう。
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
useEffect(() => {
console.log('カウント:' + count);
}, [increment]);
return (
<div>
<p>カウント: {count}</p>
<button onClick={increment}>増加</button>
</div>
);
}
このコードでは、increment
関数がuseEffect
の依存配列に含まれています。しかし、ユーザーが「増加」ボタンをクリックしてcount
が増えるたびに、increment
関数は新しい参照を持ちます。その結果、useEffect
内のconsole.log
が毎回実行されてしまいます。
これは、console.log
が頻繁に実行されるだけでなく、もしuseEffect
内で重い処理(例えば、APIからのデータ取得)を行っていた場合、パフォーマンスに影響を及ぼす可能性があります。
この問題を解決するためには、useCallback
を使ってincrement
関数をメモ化します。これにより、count
が変更されない限り、increment
関数の参照は変わらず、useEffect
は意図したタイミングでのみ実行されます。
function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(count + 1);
}, [count]);
useEffect(() => {
console.log('カウント:' + count);
}, [increment]);
return (
<div>
<p>カウント: {count}</p>
<button onClick={increment}>増加</button>
</div>
);
}
このように、useEffect
の依存配列に関数やオブジェクトを含める際は、その影響を理解し、必要に応じてuseCallback
を使って関数をメモ化することが重要です。
React を効率的に身につける勉強法は?
React には興味があっても、プログラミング学習をはじめたばかりであれば、以下のような疑問を持つかもしれません。
- Reactエンジニアとして活躍するために何をすればいい?
- 何から勉強すればいいんだろう?
- 絶対に転職を成功させるには?
React は習得が比較的難しいため、勉強法の選び方を間違うと時間・労力・お金が無駄になってしまいます…。
そのため、こちらの記事を参考に、ご自身に最適な学習方法を選んでみませんか?