Reactを学習する中で、useEffectの役割について学びました。
最初は、useEffectが何のためにあるのか、どのような場面で使用するものなのかが分かっていませんでした。
学んだ内容を整理すると、useEffectは通信、タイマー、ブラウザの機能などを画面表示と連動させるためのフックだと分かりました。
その中で、副作用、依存配列、クリーンアップについても学びました。
この記事では、自分が学んだ内容をもとに、useEffectの役割や使用する場面、使用しなくてよい場面を整理してみました。
この記事で扱う内容
この記事では、主に次の内容を扱います。
-
useEffectとは - 副作用とは
-
useEffectを使用する例 - 副作用をコンポーネント本体に直接書かない理由
-
useEffectを使用しなくてよい場面 - 依存配列とは
- クリーンアップとは
useEffectとは
useEffectは、Reactの画面表示と副作用を連動させるためのフックです。
画面が表示されたあとに処理を実行したり、指定した値が変わったときに処理をもう一度実行したりと、表示のタイミングに合わせて副作用を動かすことができます。
副作用とは
副作用とは、画面の表示とは別に、コンポーネントの外側に対して行う処理のことです。
副作用には、次のようなものがあります。
- サーバーとの通信
- ブラウザのタイトル変更
- タイマーの開始
- イベントリスナーの登録
これらの副作用を、画面表示に合わせて扱いたい場合にuseEffectを使用します。
useEffectを使用する例
カウントの値に合わせて、ブラウザのタイトルを変える例で考えます。
import { useEffect, useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `count: ${count}`;
}, [count]);
return (
<button onClick={() => setCount(prev => prev + 1)}>
{count}
</button>
);
}
このコードでは、ボタンを押すとcountが増えます。
そして、画面に表示されるcountも変わります。
さらに、useEffectの中でdocument.titleを変更しているため、ブラウザのタイトルもcountに合わせて変わります。
流れとしては、次のようになります。
-
countを使用して画面を表示する - 画面を表示したあとに
useEffectが動く -
document.titleが更新される
document.titleの変更は、画面の表示処理ではなく、ブラウザのタイトルを変更する副作用です。
副作用をコンポーネント本体に直接書かない理由
副作用をコンポーネント本体に直接書くと、画面が更新されるたびに実行されてしまいます。
たとえば、次のようにコンポーネント本体に通信処理を直接書いた場合です。
function UserList() {
fetch('/api/users');
return <div>ユーザー一覧</div>;
}
このコードでは、コンポーネント本体にfetchを直接書いています。
コンポーネント本体は、画面を表示したり更新したりするときに実行されます。
一度だけ通信したくても、コンポーネント本体が再び実行されるたびに通信も実行されます。
同じように、タイマーの開始やイベントリスナーの登録をコンポーネント本体に書くと、再実行のたびに次のようなことが起こります。
- タイマーが何個も作られる
- イベントリスナーが重複して登録される
このような処理が増えると、どのタイミングで処理が動いているのかも分かりにくくなります。
そのため、こうした副作用はコンポーネント本体に直接書くのではなく、useEffectに書きます。
useEffectを使用しなくてよい場面
useEffectは便利ですが、何でも入れる場所ではありません。
表示に使う値を計算するだけの場合
表示に使う値を計算するだけならuseEffectは必要ありません。
function Cart({ price, quantity }: { price: number; quantity: number }) {
const total = price * quantity;
return <p>合計: {total}円</p>;
}
このコードでは、priceとquantityからtotalを計算しています。
これは表示する値を計算しているだけです。
通信をしているわけでも、タイマーを動かしているわけでも、ブラウザのタイトルを変更しているわけでもありません。
そのため、useEffectを使って次のように書く必要はありません。
import { useEffect, useState } from 'react';
function Cart({ price, quantity }: { price: number; quantity: number }) {
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(price * quantity);
}, [price, quantity]);
return <p>合計: {total}円</p>;
}
この例では、priceとquantityから計算できるtotalを、わざわざstateに入れています。
この書き方にすると、totalを求めるためだけにstateとuseEffectが増えてしまい、コードの流れも追いにくくなります。
そのため、最初の例のようにstateを使わず、その場で計算すれば十分です。
ボタンを押したときだけ動く場合
ボタンを押したときだけ動かしたい処理も、基本的にはuseEffectではなくイベントハンドラに書きます。
function SaveButton() {
function handleClick() {
localStorage.setItem('saved', 'yes');
alert('保存しました');
}
return <button onClick={handleClick}>保存</button>;
}
この処理は、画面が表示されたから動くわけではなく、ボタンを押すことで動きます。
そのため、useEffectに入れるより、イベントハンドラに書く方が自然です。
localStorageに保存する処理やalertを出す処理は、副作用に見えるかもしれません。
ただし、処理が動くきっかけはユーザーの操作です。
このように、ユーザー操作がきっかけで動く処理は、まずイベントハンドラで書くようにしています。
依存配列とは
useEffectの第2引数を依存配列といいます。
依存配列には、useEffectの中で使用している値を書きます。
たとえば、props、state、コンポーネント内で定義した変数などです。
useEffect(() => {
document.title = `count: ${count}`;
}, [count]);
この例では、依存配列にcountを書いています。これにより、useEffectは次のタイミングで実行されます。
- 初回表示後に一度実行される
- その後、
countが変わるたびに再実行される
たとえば、document.titleにcountを使用しているなら、countが変わったときにタイトルも更新する必要があります。
そのため、依存配列にcountを書きます。
もしcountを依存配列に書き忘れると、countが増えてもタイトルが最初の値のまま変わらないということが起こります。
クリーンアップとは
クリーンアップとは、useEffectの中で始めた処理を止めたり、登録したものを解除したりするための関数です。
useEffectの中で関数をreturnすると、それがクリーンアップとして扱われます。
クリーンアップは、コンポーネントが画面から消えるとき(アンマウント時)や、同じuseEffectが再実行される前に実行されます。
たとえば、イベントリスナーを登録した場合は、不要になったとき解除する必要があります。
import { useEffect } from 'react';
function WindowWidth() {
useEffect(() => {
const handleResize = () => {
console.log(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <p>ウィンドウ幅を監視中です</p>;
}
このコードでは、addEventListenerでresizeイベントのリスナーを登録し、useEffectの中で関数をreturnしています。
このreturnしている関数がクリーンアップで、登録したリスナーをremoveEventListenerで解除しています。
イベントリスナー以外にも、タイマーを開始した場合など、何かを開始する処理を書いたときは、クリーンアップが必要かどうかを確認するようにしています。
まとめ
useEffectは、Reactの画面表示と、通信、タイマー、ブラウザの機能などを連動させるためのHookだと学びました。
最初は何でもuseEffectに入れようとしていましたが、整理してみると、そうではないと分かりました。useEffectを使うかどうかは、次の順番で考えるようにしています。
- ユーザー操作がきっかけなら、イベントハンドラに書く
- 表示に使う値を計算するだけなら、コンポーネント内で計算する
- 画面を表示したあとに動かす副作用なら、
useEffectを使用する
上から順に当てはめていって、最後まで残ったときにuseEffectを使うと考えるようにしています。
useEffectを使うときは、useEffect内で使用している値を依存配列に書くことと、タイマーやイベント登録のように始めた処理にはクリーンアップを書くことも、あわせて意識するようにしています。