時計とクリーンアップ

動きつづける副作用

これまでの副作用は、走ったらそれで終わりでした。タイマーは違います。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) と、関数そのものを返します。

演習

開いてからの経過秒数を数えつづける表示を作ります。

ヒント

App.jsx
App.jsx
プレビュー

できているか

  • h1 に「あおいのプロフィール」と表示されている
  • 経過秒数を出す p がある
  • 2 秒たつと「開いてから 2 秒」に増えている