投稿日:2022年11月7日
React18で導入されたStrictModeを使用すると、開発時にコンポーネントのマウントは2度行われます。この記事では、その問題について詳しく解説します。
今回、Reactを使用した開発で開発環境でのみuseEffect()が想定外の挙動をしてしまったために、メモを残すことにしました。
この予想外の挙動というのは、React側で意図された挙動であるため、ドキュメントをちゃんと読んでいれば想定通りなものである点に注意してください。
この記事のコードは以下の環境で実行しています。
今回の問題は以下のようなコンポーネントで発生しました。
import React, { useEffect } from 'react';
const App = () => {
// マウント時に1度だけ呼び出される
useEffect(()=>{
console.log(`useEffect called!`)
},[])
return (
<div>
サンプルページ
</div>
);
}
export default App;
このコンポーネントはuseEffect()に渡したログ出力の関数が、マウント時に1度だけ実行されるはずです。
しかし、実際にnpm run start
で開発用サーバーを起動してログを確認すると…
このように、本来1度しか呼ばれないはずのuseEffectのコールバックが2回呼び出されてしまっています。
この挙動は開発時のみで、以下のようにビルド後のアプリケーションでは発生しません。
$ npm run build
$ npx serve -s build -l 3000
問題の原因はcreate-react-appのテンプレートのindex.tsxにあります。
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
<App />
コンポーネントは<React.StrictMode />
コンポーネントの子要素として使用されています。StrictModeというのは、エラーなるほどの重大な問題ではないが、潜在的な問題を見つけ出すことができるコンポーネントです。StrictModeのドキュメントを読んでいくと、その中に「Ensuring reusable state」という項目があります。
この項目によると、
将来的にブラウザバックなどの機能を追加していく際に、コンポーネントは破壊と再構築に対して柔軟に対応できる必要がある。この柔軟性をチェックするためにStrictModeは、開発時にコンポーネントが初めてマウントされる時に、一度アンマウントして再度マウントし直す。
つまり、<StrictMode />
の内部のコンポーネントは開発時には常にマウントが2度行われてしまうんですね。
基本的にはマウントがやりなおされても問題なく作成するべきではありますが、この挙動によってアプリケーションの正常な挙動が確認できない場合には、下のように<StriceMode />
をコメントアウトしましょう。
// [...]
root.render(
// <React.StrictMode>
<App />
// </React.StrictMode>
);
// [...]
ありがとうございます!
しっかりと原因を調査する姿勢、素晴らしいですね!