useEffectの挙動をクイズ形式で振り返る
はじめに
普段はスタートアップでBtoB SaaSを作っているtaroと申します!
今回はuseEffect
の挙動をしっかりと理解するために、クイズ形式で振り返っていこうと思います。
普段、何気なーく使っていますが、便利なものって便利がゆえに詳しく調べる機会ってあんまりないですよね。
僕自身、最初は動くことを優先した開発の中でReactを学んできたため、こういった基礎中の基礎の学習が結構おろそかになっています。
なので今回は、useEffect
の挙動をクイズ形式で1つ1つ振り返っていこうと思います。
ぜひ1問1問考えながら、楽しく読んでいただければ幸いです!
言葉の定義
クイズ中に使用する言葉の定義です!
render
ここでは関数Componentのreturn
を実行して、DOMをブラウザに描画することを指します。
副作用編
最初は副作用が実行されるタイミングのクイズです。
Q1: 第2引数の配列に要素を渡した場合
問題:count
が出力されるのはいつでしょうか?(該当するものすべて答えよ)
- 初回render後
- 再render後に
count
が変化していた場合 - Unmount時
※Mount時、Update時だと、いくつかのphaseが含まれるため、初回render後、再render後としている。
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, [count]);
解答
- ⭕ 初回render後
- ⭕ 再render後に
count
が変化していた場合 - ❌
Unmount時
useEffect
の第2引数の配列に要素を渡した場合は、初回render後と再render後にその要素が変化していた場合に、第1引数に渡した副作用が実行されます。
Q2: 第2引数に空配列を渡した場合
問題:'hoge'
が出力されるのはいつでしょうか?(該当するものすべて答えよ)
- 初回render後
- 再render後
- Unmount時
useEffect(() => {
console.log('hoge');
}, []);
解答
- ⭕ 初回render後
- ❌
再render後 - ❌
Unmount時
useEffect
の第2引数の空配列に要素を渡した場合は、初回render時のみ第1引数のコールバック関数の中身が実行されます。
例えばリアルタイム性が不要なGETのAPI通信等は、初回render後に一度だけ実行すれば良いので、第2引数に空配列を入れてあげて不要なAPI通信を減らしてあげましょう。
Q3: 第2引数に何も渡さない場合
問題:'hoge'
が出力されるのはいつでしょうか?(該当するものすべて答えよ)
- 初回render後
- 再render後
- Unmount時
useEffect(() => {
console.log('hoge');
});
解答
- ⭕ 初回render後
- ⭕ 再render後
- ❌
Unmount時
useEffect
の第2引数に何も渡さない場合は、初回render後と再render後に第1引数のコールバック関数の中身が実行されます。
これはすべての再render後に実行されるため、もしrenderを最適化する場合は見直しが必要かもしれません。
また実際は初回render後に一度実行するだけでよいのに、空配列を渡しておらずrenderの度に処理が実行されているケースもたまに見かけます。(僕もよくやらかしてました笑)
副作用のクリーンアップ編
次は副作用のクリーンアップのクイズです。
Q4: 第2引数の配列に要素を渡した場合
問題:count
が出力されるのはいつでしょうか?(該当するものすべて答えよ)
- 初回render後
- 再render後に
count
が変化していた場合 - Unmount時
useEffect(() => {
return () => {
console.log(count);
};
}, [count]);
解答
- ❌
初回render後 - ⭕ 再render後に
count
が変化していた場合 - ⭕ Unmount時
useEffect
を使ってクリーンアップを実装する場合、第一引数のコールバック関数のreturn
の内にクリーンアップを記載します。
useEffect
の第2引数の配列に要素を渡した場合は、再render後にその要素が変化していた場合とUnmount時に、クリーンアップが実行されます。
注意するのは、クリーンアップはUnmount時だけでなく再render後にも実行される点です。
ではなぜUnmount時だけでなく、再render後にも実行されるのでしょうか?
なぜ再render後もクリーンアップが実行されるのか?
詳しくは公式ドキュメントではClass Componetで発生していた更新ロジックを書き忘れによるバグを防止するためとしています。
クラスコンポーネントでよく見られた更新ロジック書き忘れによるバグを防止することができます。
https://ja.reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update
例えば
- Mount時
- 購読を開始
- Unmount時
- 購読を解除(クリーンアップ)
のような購読処理に、購読先変更の処理を加えたい場合には、
- Mount時
- 購読を開始
- Update時
- 既存の購読を解除(クリーンアップ)
- 新しい購読を開始
- Unmount時
- 購読を解除(クリーンアップ)
のように、一度既存の購読を解除する必要があります。
こういった状態の変化に対応する際に、Unmount時とUpdate時の両方に同様の処理を書く必要があったのが、useEffect
のクリーンアップはUnmount時と更新時の両方で実行されるため処理を1箇所に書くだけで良くなり、Update時のクリーンアップを書き忘れることによるバグが発生しずらくなりました。
少し説明が長くなりましたが、次の問題に進みます。
Q5: 第2引数に空配列を渡した場合
問題:'hoge'
が出力されるのはいつでしょうか?(該当するものすべて答えよ)
- 初回render後
- 再render後
- Unmount時
useEffect(() => {
return () => {
console.log('hoge');
};
}, []);
解答
- ❌
初回render後 - ❌
再render後 - ⭕ Unmount時
第2引数に空配列を渡した場合は、Unmount時のみクリーンアップが実行されます。
これはQ2と同様に考えると自然な動きですね。
Q6: 第2引数に何も渡さない場合
問題:'hoge'
が出力されるのはいつでしょうか?(該当するものすべて答えよ)
- 初回render後
- 再render後
- Unmount時
useEffect(() => {
return () => {
console.log('hoge');
};
});
解答
- ❌
初回render後 - ⭕ 再render後
- ⭕ Unmount時
第2引数を渡さない場合は、再render後とUnmount時にクリーンアップが実行されます。
これもQ3で述べたことと同様に、不必要にクリーンアップを実行している場合は第2引数を渡して、処理を最適化してあげましょう。
副作用×クリーンアップ編
最後に副作用とクリーンアップが両方ある場合のクイズです。
Q7: 第2引数の配列に要素を渡した場合
問題:count
が0
→1
に変化した場合に、consoleには何が出力されるでしょうか?
consoleに出力される順番と出力されるcount
の値が何かをお考えください。
useEffect(() => {
console.log('副作用', count)
return () => {
console.log('クリーンアップ', count);
};
}, [count]);
解答
クリーンアップ 0
副作用 1
答えは、クリーンアップが先に実行され、またクリーンアップでは変化前の要素の値、副作用では変化後の要素の値を参照します。
ではなぜクリーンアップでは変化前の値を参照するのでしょうか?
なぜクリーンアップでは変化前の値を参照するのか?
これはクリーンアップの目的を考えると自然に理解できます。
クリーンアップはUnmount時と再render後にその前の副作用を解除するために使われ、解除するためには通常、変化前の値を使用します。
例えば、id
の変化によって購読先を帰る場合は、以下のような実装になるでしょう。
const [id, setId] = useState(initId)
useEffect(() => {
// 購読開始
subscribe(id)
return () => {
// 購読解除
unsubscribe(id)
};
}, [id]);
この場合、idが1
→2
に変化した時にunsubscribe()
に渡るべきid
は、購読を解除するid
である1
です。
以上のような用途から、クリーンアップでは変化前の値を参照していることが自然に理解できます。
お世話になったページ
まとめ
7問のクイズを通してuseEffect
の挙動を振り返ってみました。
みなさんは何問正解できたでしょうか?
個人としてはなんとなく使っていた部分が、しっかり人に説明できるレベルまで理解が深まりました。
また改めてHooksは使いやすく作られているなーと感じました。(その分、若干見えづらくなっている部分もありますが)
冒頭にも述べましたが、こういった便利なものはなんとなくで使いがちですが、しっかりと使い方や作り手の意図を理解した上で、使いこなしていきたいと思います!
Discussion
勉強になる記事をありがとうございました!
普段使っているはずなのに全問正解できなかったので改めて学ぶいい機会となりました。
問題形式なのも面白かったですが、スクロールしすぎて意図せず答えが見えてしまうことがあったため、Zennの記法にあるアコーディオンを使って答えを隠してみるのはどうでしょうか?
ZennのMarkdown記法一覧
のような感じになるかと思います!
ありがとうございます!
アコーディオン知りませんでした…!
良さそうなので採用させていただき更新しましたー!