React useEffect がマウント時に2回実行される

Programming
公開: 2024-10-11

「開発時に」useEffect がマウント時に2回実行されるのは仕様

React 18からの仕様です。StrictModeが有効な開発時のみ、すべてのコンポーネントについてuseEffectはこのように動作します。

Imgur

useEffectでマウント時に1回fetchでAPIにアクセスして情報をとってくると 2回fetchされている React18の、StrictModeの追加機能によるものだった。

when running in “strict mode“ React will intentionally double-render components for in order to flush out unsafe side effects.
「ストリクトモード」で実行すると、安全でない副作用を洗い出すために、React は意図的にコンポーネントを二重レンダリングします。

どういうことか

ChatRoomコンポーネントが大きなアプリケーションの一部で、多くの異なる画面が備わっていたとします。ユーザーがはじめに開くのはChatRoomのページです。コンポーネントはマウントされて、副作用のconnect()が呼び出されます。そのあと、別の画面たとえば設定ページに移ったとしましょう。ChatRoomコンポーネントがアンマウントされます。 そのうえで、ユーザーが[戻る]をクリックしたら、ChatRoomはふたたびマウントされるのです。すると、2度目の接続が設定されます。けれど、はじめの接続が破棄されていません。ユーザーがアプリケーション内のページを移動することにより、接続がたまってしまうのです。

こうしたバグは、手作業による広範なテストを行わないかぎり、見逃されやすくなります。問題がすばやく見つけられるように、Reactは開発時にはすべてのコンポーネントをはじめのマウントの直後に再マウントするのです。「Connecting...」のログを2回見ることで、本当の問題に気づきやすくなります。コンポーネントがアンマウントされたとき、コードは接続を閉じていないということです。 この問題を解決するには、useEffectからクリーンアップ関数を返してください。

つまり、マウントが複数回実行されておかしな動作をするコードは悪いコード(バグ)なので、 開発時にそれが分かるように、わざと2回実行されるようになっている。

対策というか考え方

開発時に useEffect がマウント時に2回実行される。ただし、運用環境では1回しか実行されない。 これは React としては正しい動作なので、この思想に沿った実装をする必要がある。 「複数のコンポーネントが並列で非同期(async)に実行され、マウント後に再マウントされる事態もあり得る」ので適切に処理せよ、という話らしい。

  • 「StrictModeを無効にする」はNGにしたほうがよさげ
  • クリーンアップ関数を実装して、クリーンアップを行う

通常は、クリーンアップ関数を実装して対応します。クリーンアップが行うのは、 エフェクトの実行していたことをすべて停止あるいはもとに戻すことです。 大抵のユーザーは、(本番環境の)1度だけのエフェクトと、(開発時の)副作用→クリーンアップ→副作用の実行の違いには気づきません。

Reactがクリーンアップ関数を呼び出すのは、つねにエフェクトが再実行される前です。 そして、最後にコンポーネントがアンマウント(削除)されるときに、クリーンアップを行います。

useEffect(() => {
	connect();
	return () => {  ★★ クリーンアップ処理
		disconnect();
	};
}, []);
useEffect(() => {
	let ignore = false;
	async function startFetching() {
		const json = await fetchTodos(userId);
		if (!ignore) {
			setTodos(json);
		}
	}
	startFetching();
	return () => {  ★★ クリーンアップ処理
		ignore = true;
	};
}, [userId]);

どのようにクリーンアップを行うかは処理ごとに考える必要がある。

参考

React + TypeScript: React 18でコンポーネントのマウント時にuseEffectが2度実行されてしまう useEffectがマウント時に2回実行される React(公式)Synchronizing with Effects