useEffectとは
React Hooksの一つで、コンポーネントがレンダリングされたときに副作用を実行するために使用されます。
コンポーネントはJSXを構築する場所であってJSXの構築に直接関係のない処理は全て副作用として扱われます。
それらの副作用は純粋関数の観点などからuseEffect
やイベントハンドラ内に記述する必要があります。
Reactにおける副作用とは、主に下記のような処理です。
- DOM操作
- API通信
- console.log
- ファイルへの書き込み
- state/propsの変更
- オブジェクトまたはその内部のプロパティへの代入
- 配列のpush()
構文
第一引数に実行したい処理としてコールバック関数、第二引数に[]
で囲ったstateを渡します。
この第二引数は依存配列と呼ばれ、依存配列に含めたstateの値に更新があった際に、第一引数のコールバック関数が実行されます。
useEffect(() => {
処理
}, [state]);
シンプルなコード、使用例
主な使い方は下記3パターンです。
- レンダリングされたタイミングで処理を実行
- 初回レンダリング時のみ処理を実行
- 特定のstate(依存配列)の更新があった際に処理を実行
レンダリングされたタイミングで処理を実行
第二引数を記述しない場合、更新用関数が実行された場合など、レンダリングされるタイミングで処理が実行されます。
要するにuseEffect
の外側で関数を実行している場合と同じような挙動になります。
下記コンポーネントに二つのconsoleがありますが、同じタイミングで実行されます。
import { useState, useEffect } from "react";
const Sample = () => {
console.log("useEffectの外側だよ");
useEffect(() => {
console.log("レンダリングがあったよ");
});
return (
...
);
};
export default Sample;
初回レンダリングのみ処理を実行
依存配列を空で渡すと初回レンダリング時のみ処理が実行されます。
コード例として、タイマーのようなコンポーネントを作成します。
下記コンポーネントを実行してみると1秒ごとにカウントアップされるコンポーネントになります。
useEffect
のコールバック関数でconsoleを記述していますが、依存配列を空で渡しているので初回レンダリング時のみ処理が実行され、consoleは一度しかされません。
import { useState, useEffect } from "react";
const Sample = () => {
const [time, setTime] = useState(0);
useEffect(() => {
console.log("useEffectが実行されたよ");
window.setInterval(() => {
setTime((prev) => prev + 1);
}, 1000);
}, []);
return (
<h3>
<time>{time}</time>
<span>秒経過</span>
</h3>
);
};
export default Sample;
window.setInterval()は第二引数に記述したm秒の遅延間隔でコールバック関数が実行されるメソッドです。
特定のstate(依存配列)の更新があった際に処理を実行
先ほどのコードに追加します。
import { useState, useEffect } from "react";
const Sample = () => {
const [time, setTime] = useState(0);
useEffect(() => {
console.log("useEffectが実行されたよ");
window.setInterval(() => {
setTime((prev) => prev + 1);
}, 1000);
}, []);
// 📌 下記追記
useEffect(() => {
console.log("timeの値が変わったよ");
}, [time]);
return (
<h3>
<time>{time}</time>
<span>秒経過</span>
</h3>
);
};
export default Sample;
すると、依存配列に含めたstate、time
に更新があるたびに処理が実行されていることがわかります。
無限ループになってしまうので依存配列のstate値をuseEffectのコールバックの中で書き換えてはいけません。
useEffectのクリーンアップ処理の使い方
useEffect
のコールバック関数の戻り値に対してreturn
で関数を渡すことができます。
ここでreturn
された関数をクリーンアップ関数と言います。
クリーンアップ関数を渡した場合、依存配列に更新があった際や、そのコンポーネントが削除された際に、クリーンアップ関数が実行されます。
-
依存配列の値更新時とコンポーネント削除(アンマウント)時
クリーンアップ関数 →useEffect
のコールバック関数 の順番で実行 -
依存配列を空で渡した場合
コンポーネント削除(アンマウント)時にクリーンアップ関数が実行
import { useEffect, useState } from "react";
const Example = () => {
const [isDisp, setIsDisp] = useState(true);
return (
<>
{isDisp && <Timer />}
<button
onClick={() => {
setIsDisp((prev) => !prev);
}}
>
クリック
</button>
</>
);
};
const Timer = () => {
const [time, setTime] = useState(0);
useEffect(() => {
console.log("Timerコンポーネントが実行されたよ");
window.setInterval(() => {
setTime((prev) => prev + 1);
}, 1000);
// 📌 下記がクリーンアップ関数
return () => {
console.log("Timerコンポーネントが削除されたよ");
};
}, []);
return (
<h3>
<time>{time}</time>
<span>秒経過</span>
</h3>
);
};
export default Example;
こちらのコンポーネントを実行してみると下のようなタイマーとボタンが表示され、useEffect
の初回マイント時に、console
が出力されているかと思います。
クリックボタンを押下してTimer
コンポーネントを削除(アンマウント)します。
するとクリーンアップ関数が実行されconsole
出力されることがわかると思います。
上記サンプルコードではsetInterval()
の処理を行なっていますが、Timer
コンポーネントをアンマウントした際には、不要になる処理です。
このような時にuseEffect
で実行した処理の何らかの後始末を行うためにクリーンアップ関数を使用します。
クリーンアップ関数を使用しなかった場合、コンポーネントをアンマウントしても裏ではsetInterval()
処理が行われ続けメモリリークに繋がります。
下記のようにuseEffect
の処理を書き換えて、ボタン押下でTimer
コンポーネントの非表示を行なってください。処理が行われ続けることがわかるかと思います。
useEffect(() => {
console.log("Timerコンポーネントが実行されたよ");
window.setInterval(() => {
console.log("setInterval処理"); // 📌 追記
setTime((prev) => prev + 1);
}, 1000);
// 📌 クリーンアップ関数コメントアウト
// return () => {
// console.log("Timerコンポーネントが削除されたよ");
// };
}, []);
useLayoutEffectとは?
Reactのフックの一つとしてuseEffect
と表示に似た名前のuseLayoutEffect
というフックがあります。
構文や依存配列、クリーンアップ関数などの使い方はuseEffect
と同じです。
これまで記述したサンプルコードのuseEffect
をuseLayoutEffect
に書き換えても同じように動作します。
useEffect
との違いは、実行されるタイミングです。
useEffect
は一度画面が描写された後にuseEffect
のコールバック関数が実行されるのに対し、useLayoutEffect
は 画面が描写される前に useLayoutEffect
のコールバック関数が実行されます。
useLayoutEffect
は、useEffect
より実行されるタイミングが早い ✍🏻
カスタムフックとは?
useState
などのReactのHookを内部で使用した関数のことを指します。
React Hookを関数に切り出すことで再利用ができるようになります。
React Hookは基本的に関数コンポーネントのトップレベルでしか呼べないのですが、
use〇〇のようにuseから始まる関数名の中では例外的にReact Hookを呼ぶ事ができます。
そしてカスタムフックであることを明示するために、カスタムフックの命名はuse〇〇にする必要があります。
シンプルな使用例
まずは下記コンポーネントを作成します。
import { useState } from "react";
const Example = () => {
const [count, setCount] = useState(0);
return (
<>
<div>カウント: {count}</div>
<button
onClick={() => {
setCount((prev) => prev + 1);
}}
>
クリック
</button>
</>
);
};
export default Example;
ボタンクリックごとにカウントアップされるコンポーネントです。
こちらをカスタムフックを利用してReact Hook部分を切り分けてみたいと思います。
新しくHooks.js
を作成し、そこにReact HooksのuseState
部分を切り分けます。
単純にuseState
部分を切り分けたのに加え、return
を追加する必要があることに注意してください。
Example.jsで使用するstate
と更新用関数をreturn
します。
Example.jsではカスタムフックuseHooks
を実行し、返ってきたcount
、setCount
を分割代入して、使用するという流れです。
import { useState } from "react";
const useHooks = () => {
const [count, setCount] = useState(0);
// 📌 Example.jsで使用するstateと更新用関数をreturn
return {
count,
setCount,
};
};
export default useHooks;
import useHooks from "./Hooks";
const Example = () => {
// 📌 useHooksでreturnされている値を分割代入
const { count, setCount } = useHooks();
return (
<>
<div>カウント: {count}</div>
<button
onClick={() => {
setCount((prev) => prev + 1);
}}
>
クリック
</button>
</>
);
};
export default Example;
このようにカスタムフックを用いることで、React Hooksを用いた処理を再利用できることがわかるかと思います。