useEffectのコールバック関数とcleanUp関数の実行タイミング、正しく説明できますか?
TL;DR
「正しく説明できないな」となった人は useEffect を使ったり useEffect の関連記事を読む前に、ここで一緒に理解していきましょう。
この記事を最後まで読めば useEffect の基本についてはバッチリになると思います。
useEffect の基本
以下のような形が基本形となります。
第一引数には useEffect のコールバック関数、第二引数には依存配列と呼ばれるものを渡します。
依存配列に値を渡した場合、その値が更新された際にコールバック関数が実行されます。
useEffect(() => {
console.log("useEffect called");
}, [])
useEffect の cleanUp 処理
ところで useEffect には cleanUp 処理を用意することができます。
どういうことかというと、コンポーネントが消滅した際(アンマウント or 再レンダリングされたとき)に実行される処理で以下のように定義します。
useEffect(() => {
console.log("useEffect called");
return () => {
console.log("cleanUp");
}
}, [])
このコードの例だとコンポーネントが消滅した際にはコンソールに"cleanUp"が表示されることになります。
検証
ではみなさん、以下の 3 つの状況で useEffect の本体と cleanUp 処理がどのタイミングで実行されるか即答できますでしょうか?
- 依存配列が空の時
- 依存配列に値が設定されているとき
- 依存配列が定義されていないとき
この 3 つのパターンについて、以下のサンプルコードで確認していきたいと思います。
import { useState } from "react"
import Counter from "../Components/counter"
export default function Test() {
const [isDisp, setIsDisp] = useState(true)
return (
<>
<div>
{isDisp && <Counter />}
</div>
<button className='border border-black text-black font-bold py-2 px-4 mt-2' onClick={() => setIsDisp(prev => !prev)}>表示/非表示</button>
</>
)
}
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(100)
useEffect(() => {
console.log("useEffect called");
return () => {
console.log("cleanUp");
}
}, [])
return (
<>
<h1>{count}</h1>
<button onClick={() => setCount(prev => prev + 1)} className='border border-black text-black font-bold mr-2 py-2 px-4'>+</button>
<button onClick={() => setCount(prev => prev - 1)} className='border border-black text-black font-bold py-2 px-4 '>-</button>
</>
)
}
このコードは以下のようなカウンターコンポーネントです。
それぞれのコンポーネントの役割ですが、counter コンポーネントは useEffect を持っており、そこに指定したコールバック関数と cleanUp 関数の動きを確認します。
一方で親コンポーネントとなるTest
コンポーネントはマウント or アンマウントを制御するためコンポーネントです。「表示・非表示」ボタンを押下することでマウント or アンマウントを発生させます。
依存配列が空の場合
まずは依存配列が空の場合から確認します。
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(100)
useEffect(() => {
console.log("useEffect called");
return () => {
console.log("cleanUp");
}
}, [])
return (
<>
<h1>{count}</h1>
<button onClick={() => setCount(prev => prev + 1)} className='border border-black text-black font-bold mr-2 py-2 px-4'>+</button>
<button onClick={() => setCount(prev => prev - 1)} className='border border-black text-black font-bold py-2 px-4 '>-</button>
</>
)
}
初回のレンダリングが終わったタイミングでコンソールを確認します。
"useEffect called"が一度だけ出力されているのがわかります。
では、「+」ボタンを押下してみます。ここで再レンダリングが走ることになります。
しかし、このタイミングではコンソールに"useEffect called"も"cleanUp"も出力されていません。
最後に「表示・非表示」ボタンを押下してアンマウントを発生させます。
ここでCounter
コンポーネントが DOM ツリーからアンマウントされたので、cleanUp 関数が実行されコンソールには"cleanUp"が表示されました。
すなわち、依存配列が空の場合は初回のレンダリング時に useEffect のコールバック関数が実行され、アンマウント時には cleanUp 関数が実行されている。
しかし、state の更新に伴う再レンダリングが発生した際には useEffect のコールバック関数も cleanUp 関数も実行されていないということです。
依存配列に値を指定した場合
では次に、依存配列に値を指定した場合を見てみます。
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(100)
useEffect(() => {
console.log("useEffect called");
return () => {
console.log("cleanUp");
}
}, [count])
return (
<>
<h1>{count}</h1>
<button onClick={() => setCount(prev => prev + 1)} className='border border-black text-black font-bold mr-2 py-2 px-4'>+</button>
<button onClick={() => setCount(prev => prev - 1)} className='border border-black text-black font-bold py-2 px-4 '>-</button>
</>
)
}
まずは初回のレンダリングが終わったタイミングでコンソールを確認します。
ここでは先ほどと変わらず、"useEffect called"が一度だけ出力されているのがわかります。
では、「+」ボタンを一度押します。
すると今度は「+」ボタンを押下したタイミングで"cleanUp"と"useEffect called"が出力されたのがわかると思います。
最後に「表示・非表示」ボタンを押下してアンマウントを発生させます。
ここでCounter
コンポーネントが DOM ツリーからアンマウントされたので、cleanUp 関数が実行されコンソールには"cleanUp"が表示されました。
すなわち、依存配列に値を指定した場合は初回のレンダリング時に useEffect のコールバック関数が実行され、アンマウント時には cleanUp 関数が実行されている。
また、state の更新に伴う再レンダリングが発生した際には useEffect のコールバック関数も cleanUp 関数も実行されているということです。
依存配列を省略した場合
では最後に、依存配列を省略した場合を見てみます。
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(100)
useEffect(() => {
console.log("useEffect called");
return () => {
console.log("cleanUp");
}
})
return (
<>
<h1>{count}</h1>
<button onClick={() => setCount(prev => prev + 1)} className='border border-black text-black font-bold mr-2 py-2 px-4'>+</button>
<button onClick={() => setCount(prev => prev - 1)} className='border border-black text-black font-bold py-2 px-4 '>-</button>
</>
)
}
まずは初回のレンダリングが終わったタイミングでコンソールを確認します。
ここでは先ほどと変わらず、"useEffect called"が一度だけ出力されているのがわかります。
では、「+」ボタンを一度押します。
すると今度は「+」ボタンを押下したタイミングで"cleanUp"と"useEffect called"が出力されたのがわかると思います。
最後に「表示・非表示」ボタンを押下してアンマウントを発生させます。
ここでCounter
コンポーネントが DOM ツリーからアンマウントされたので、cleanUp 関数が実行されコンソールには"cleanUp"が表示されました。
すなわち、依存配列を省略した場合は初回のレンダリング時に useEffect のコールバック関数が実行され、アンマウント時には cleanUp 関数が実行されている。
また、state の更新に伴う再レンダリングが発生した際には useEffect のコールバック関数も cleanUp 関数も実行されているということです。
まとめ
以下の図のイメージです。
Discussion