時計とクリーンアップ
動きつづける副作用
これまでの副作用は、走ったらそれで終わりでした。タイマーは違います。setInterval は一度呼ぶと、止めるまでずっと動きつづけます。
useEffect(() => {
const id = setInterval(() => {
setSeconds((s) => s + 1);
}, 1000);
}, []);これでも1秒ごとに数は増えます。ただし、この部品が画面から消えたあともタイマーは動きつづけます。消えた部品の state を更新しようとして、静かに無駄な処理が積み上がります。同じ画面を何度も開け閉めすれば、タイマーはその回数だけ増えていきます。
クリーンアップ関数
そこで useEffect に渡す関数から、後始末の関数を return します。
useEffect(() => {
const id = setInterval(() => {
setSeconds((s) => s + 1);
}, 1000);
return () => clearInterval(id);
}, []);返した関数はクリーンアップ関数と呼ばれ、React が次の2つのときに呼んでくれます。
- 部品が画面から消えるとき (アンマウント)
- 依存配列の値が変わって、effect をやり直す直前
「用意したものは自分で片づける」と覚えてください。タイマー、イベントリスナー、通信の中断など、始めたら止める必要があるものにはすべて必要です。
更新関数を使う理由
タイマーの中で setSeconds(seconds + 1) と書くと、effect が作られた時点の seconds をずっと見つづけるので 1 で止まります。setSeconds((s) => s + 1) のように前の値を受け取る形にすれば、依存配列が空でも正しく増えていきます。
つまずきどころ
return clearInterval(id) は間違いです。それは「その場で止めた結果」を返しています。return () => clearInterval(id) と、関数そのものを返します。
演習
開いてからの経過秒数を数えつづける表示を作ります。