Menu

ReactのStrict Mode:エフェクトとレンダリングが2回実行される理由

StrictModeは開発中だけ働くラッパーで、コンポーネントを2回レンダリングし、マウント時にエフェクトのセットアップ、クリーンアップ、セットアップを実行し、非推奨のAPIについて警告します。純粋でないレンダリングやクリーンアップの欠けたエフェクトを早めに見つけられます。本番では何もしません。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

<StrictMode> は、中のすべてのものに対して、開発中だけ追加のチェックを有効にするReactのコンポーネントです。各コンポーネントを2回レンダリングし、マウント時にすべてのエフェクトのセットアップ、クリーンアップ、セットアップを実行し、refコールバックを2回実行し、非推奨のAPIについて警告します。開発中に useEffect が「2回実行される」のはこのためです。本番ビルドでは何もしません。

有効にするには、通常は main.jsx でアプリを包みます。

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';

createRoot(document.getElementById('root')).render(
    <StrictMode>
        <App />
    </StrictMode>,
);

このページのプレビューは本番ビルドのように動くので、StrictModeの効果は目に見えません。代わりに下の各例は、StrictModeが見つけるためにある種類のバグを持っていて、それを手で起こせるようにしています。ここでは、エフェクトが document にクリックのリスナーを追加し、決して削除しません。パネルを何回か隠したり表示したりしてから、ページをクリックしてください。

Showを押すたびにリスナーが1つ増え、どれも削除されないので、1回のクリックでマウントの回数だけログが出ます。パネルを開いたり閉じたりするだけでユーザーが起こせるリークです。StrictModeならこれを最初のマウントで示します。開発中はマウント、アンマウント、マウントを行うので、ボタンに触れる前から、クリックごとに2行のログが表示されるでしょう。

エフェクト:セットアップ、クリーンアップ、セットアップ

マウント時、StrictModeはエフェクトを実行し、次にそのクリーンアップを、そしてもう一度エフェクトを実行します。ユーザーが画面を離れて戻ってきたときに起きることをシミュレートし、クリーンアップがセットアップを元に戻すことを確かめているのです。元に戻せていれば、2回目のセットアップは1回のセットアップとまったく同じ状態になり、何もおかしなことは見えません。戻せていなければ、バグがすぐに現れます。

ロガーの直し方は、追加したのと同じ関数を削除するクリーンアップです。

好きなだけ切り替えてください。ロガーが表示されている間、クリックごとに1行だけログが出ます。開発中のStrictModeでは、コンソールは setup、cleanup、setup で始まり、それでもアプリは正しく動きます。この順番は健全なエフェクトのしるしで、無効にすべき問題ではありません。

クリーンアップが必要なよくあるエフェクト:イベントリスナー、setInterval と setTimeout、購読、WebSocketやチャットの接続、コンポーネントが消えていたらレスポンスを無視すべきリクエストです。それぞれのクリーンアップのパターンはuseEffectのページで紹介しています。

レンダリング:2回呼ばれる

StrictModeは、レンダリングごとにコンポーネント関数も2回呼びます(useState、useMemo、useReducer に渡す関数や、クラスの constructor、render、shouldComponentUpdate も)。Reactは一方の呼び出しの結果を保ち、もう一方を捨てます。純粋なコンポーネントは2回とも同じJSXを返すので、何も変わりません。

レンダリング中に自分の外の何かを変えるコンポーネントは純粋ではなく、2回呼ぶとその変更が2回起きます。次のコンポーネントは、propとして受け取った配列に「rendered at」の項目を追加します。

再レンダリングのたびに、誰も頼んでいない項目が追加されます。ここではクリックした後にしか気づきません。StrictModeなら、最初のレンダリングからすでに新しい項目が1つではなく2つ表示されるでしょう。直し方は、直接変更せずにレンダリング中に計算することです:const shown = [...entries, 'extra']。

React 19では細かな点が1つ加わりました。2回のレンダリングの間、useMemo と useCallback は2回計算する代わりに、1回目の呼び出しの結果を再利用します。

refコールバック

React 19から、StrictModeはマウント時にrefコールバックも2回実行します。付ける、クリーンアップ、付ける、です。リスナーを追加したり、ノードをどこかに登録したりするrefコールバックは、エフェクトとまったく同じようにクリーンアップを返す必要があります。

<div
    ref={(node) => {
        const observer = new ResizeObserver(onResize);
        observer.observe(node);
        return () => observer.disconnect();
    }}
/>

非推奨のAPIの警告

StrictModeは、廃止される予定のAPIについて警告します。主にクラスコンポーネントのもので、古い UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps、UNSAFE_componentWillUpdate のライフサイクルなどです。文字列refや findDOMNode のように、以前警告していたAPIのいくつかは、React 19で完全に削除されました。

アプリの一部だけで有効にする

<StrictMode> は普通のコンポーネントなので、ツリーの一部だけを包めます。大きなアプリで導入するときに役立ちます。新しい画面から始めて、範囲を広げていきましょう。

function App() {
    return (
        <>
            <Header />
            <StrictMode>
                <Sidebar />
                <Content />
            </StrictMode>
            <Footer />
        </>
    );
}

Sidebar と Content(とそれらが描画するすべてのもの)はチェックを受け、Header と Footer は受けません。strictな部分の中にあるサブツリーだけを無効にする方法はありません。

デフォルトで有効になっている場所:

  • Vite:Reactのテンプレートは main.jsx で <App /> を <StrictMode> で包んでいます。
  • Next.js:App Routerはデフォルトで有効にしていて、next.config.js の reactStrictMode で制御します。

本番では何もしない

本番ビルドは <StrictMode> を無視します。レンダリングは1回、エフェクトの実行も1回で、警告もありません。そのため2回の呼び出しがユーザーに負担をかけることはなく、デプロイ前にラッパーを削除する理由もありません。

開発中に2回のエフェクトで何かが壊れるなら、たとえばリクエストが2回送られたり、分析のイベントが2回記録されたりするなら、それは情報です。たいていの直し方はクリーンアップです(リクエストなら ignore フラグ、接続なら切断)。SDKの初期化のように、本当にアプリの読み込みごとに1回だけ起きるべきことは、どのコンポーネントの外でもあるモジュールのトップレベルで実行するか、モジュールレベルのフラグで守ってください。

let didInit = false;

function App() {
    useEffect(() => {
        if (didInit) return;
        didInit = true;
        initAnalytics(); // runs once per page load, even in StrictMode
    }, []);
}

2回目のエフェクトの実行をrefで飛ばす、よくある回避策は避けてください。クリーンアップの欠落を直さずに警告を隠すだけで、上のロガーのように、コンポーネントが本当にアンマウントされてもう一度マウントされた最初のときに、バグが戻ってきます。

よくある質問

ReactのStrictModeは何をするものですか?

開発中だけ、中のすべてのコンポーネントを2回レンダリングし、コンポーネントのマウント時に各エフェクトのセットアップ、クリーンアップ、セットアップを実行し、refコールバックも同じように2回実行し、非推奨のAPIについて警告します。追加の呼び出しによって、純粋でないレンダリングやクリーンアップの欠けたエフェクトがあらわになります。

useEffectが2回実行されるのはなぜですか?

コンポーネントが <StrictMode> の中にあり、開発ビルドを実行しているからです。Reactはそれをマウントし、アンマウントをシミュレートし(クリーンアップが実行されます)、もう一度マウントします。2回目のセットアップでバグが起きるなら、そのエフェクトにはクリーンアップが欠けています。

StrictModeは本番に影響しますか?

いいえ。本番ビルドは、<StrictMode> の有無にかかわらず、1回だけレンダリングし、各エフェクトを1回だけ実行します。コードに残しておいて構いません。

StrictModeを無効にするには?

<StrictMode> のラッパーを削除するか(通常は main.jsx か index.js にあります)、next.config.js で reactStrictMode: false を設定します。ただ、たいていはクリーンアップを直すほうがよいです。2回の実行は、本物のユーザーが遭遇しうるバグを指しているからです。

console.logが2回出力されるのはなぜですか?

開発中、StrictModeはレンダリングごとにコンポーネント関数を2回呼ぶからです。React DevToolsの設定で、2回目の呼び出しのログを灰色にしたり隠したりできます。

Coddyのプログラミング言語のイラスト

Coddyでコードを学ぼう

始める