---
title: "Redux DevToolsの使い方｜React-Reduxの接続とステートの追跡"
url: "https://www.issoh.co.jp/tech/details/3060/"
published: 2024-07-16
updated: 2026-08-04
categories: ["React"]
publisher: "株式会社一創"
---

# Redux DevToolsの使い方｜React-Reduxの接続とステートの追跡

Reduxを入れる実利のうち、他の状態管理ライブラリで代替しにくいものがRedux DevToolsです。ディスパッチされたアクションが時系列で積まれ、各時点のステートを差分で比較でき、任意の時点まで画面を巻き戻せます。再現手順の長い不具合ほど、この履歴が効きます。

この記事では react-redux 9.3.0 を前提に、ReactアプリとReduxストアの接続、Redux DevToolsの導入と画面の読み方、動かないときの切り分けを扱います。Redux Toolkit そのものの構成やcreateSlice・RTK Queryの実装は[Redux Toolkitとは？createSliceとRTK Queryの実装と採用判断【2026年8月版】](/tech/details/16013/)で解説しているため、本稿では接続とデバッグに絞ります。

## まとめ：接続とDevToolsで押さえる要点

- react-redux 9.3.0 の peerDependencies は `react: ^18.0 || ^19` です。React 17以前のプロジェクトでは npm 7 以降がインストール自体を `ERESOLVE` で止めるため、react-redux 8 系を指定します。
- DevToolsの接続は `configureStore` なら設定不要です。`devTools` の既定値が true のため、拡張機能を入れるだけで繋がります。
- 不具合調査で最初に開くタブはStateではなくDiffです。「関係ない値まで変わる」のか「変わるはずの値が変わらない」のかを一目で切り分けられます。
- アクションは表示されるのにステートが空という症状は、ストア設定ではなく非シリアライズ値（`Map`、`Date`、クラスインスタンス）の混入が原因です。
- `useSelector` が毎回新しいオブジェクトを返すと、ストア更新のたびに再レンダリングが走ります。参照等価で比較される点が落とし穴です。

## React-Reduxの接続｜Provider・useSelector・useDispatch

### Providerの配置とreact-redux 9のReactバージョン要件

ストアをReactツリーへ渡す役目は `Provider` が担います。アプリのルートを包むだけで、配下のどのコンポーネントからもストアへアクセスできるようになります。

```
// src/main.jsx
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { store } from './app/store'
import App from './App'

const root = createRoot(document.getElementById('root'))
root.render(
  <Provider store={store}>
    <App />
  </Provider>
)
```

ここにバージョンの制約が1つあります。react-redux 9.3.0 の peerDependencies は `react: ^18.0 || ^19` で、この指定に optional は付いていません。npm 7 以降は peer 依存の衝突を警告ではなく `ERESOLVE` エラーとして扱うため、React 17 のプロジェクトではインストール自体が失敗します。`--legacy-peer-deps` で強引に入れた場合、v9 が React 18 の `useSyncExternalStore` を前提に shim を廃止した経緯から、実行時に破綻する点にも注意してください。公式の移行ガイドも「React-Redux v9はReact 18を必須とし、React 16や17をサポートしない」と明記しています。Reactを上げられない事情があるなら、react-redux 8 系をバージョン指定でインストールするのが唯一の解です。

### useSelectorの選択範囲と再レンダリングの抑制

ストアから値を読むのが `useSelector`、アクションを送るのが `useDispatch` です。

```
import { useSelector, useDispatch } from 'react-redux'
import { increment } from '../features/counter/counterSlice'

function Counter() {
  const value = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()
  return <button onClick={() => dispatch(increment())}>{value}</button>
}
```

性能上の落とし穴はセレクタの戻り値の作り方にあります。react-reduxは既定で戻り値を参照等価（`===`）で比較するため、`useSelector((state) => ({ a: state.a, b: state.b }))` のように毎回新しいオブジェクトを返す書き方では、ストアが更新されるたびに必ず再レンダリングが走ります。プリミティブ値ごとに `useSelector` を分けて呼ぶのが最も単純な回避策です。複数値をまとめたいなら、RTKが依存に持つreselect（RTK 2.12.0 の指定は `^5.1.0`、現在の解決先は 5.2.0）でメモ化セレクタを作ります。

Reactの再レンダリング最適化そのものは、React Compilerの登場で前提が動いています。手動メモ化との線引きは[React Compilerとは？自動メモ化の仕組みと導入方法・useMemoとの違いを解説](/tech/details/9415/)にまとめました。

### useDispatchとconnectの使い分け

`connect` と `mapStateToProps` は今も動作します。ただし新規コードで選ぶ理由は薄れました。フック版はコンポーネントを高階関数で包まないぶん型推論が素直で、TypeScriptとの相性が明確に良いためです。`connect` が実務で残る用途は、クラスコンポーネントが残存していてフックを呼べないケースに限られます。既存プロジェクトを一括で書き換える必要はありません。触るコンポーネントから順にフックへ寄せる進め方で十分です。

なお、ローカルに閉じた状態までReduxへ持ち上げる必要はありません。開閉フラグや入力中のテキストは `useState`、再レンダリングを起こさず値を保持したいだけなら `useRef` が適します。判断基準は[ReactのuseRefの使い方｜useStateとの違い・DOM参照・値の保持を実例で解説](/tech/details/3279/)と[Reactフックの基本と導入：React 16.8で追加された新機能の概要](/tech/details/3671/)で整理しています。

## Redux DevToolsの導入｜拡張機能の入手とストア側の設定

### 拡張機能の入手先と接続確認

Redux DevToolsはブラウザ拡張として配布されています。Chromeは Chrome Web Store、FirefoxはMozilla Add-ons から入手します。Electronアプリに組み込む場合は electron-devtools-installer に `REDUX_DEVTOOLS` を指定してください。

導入できたかどうかは、開発サーバーを起動してブラウザの開発者ツールに「Redux」タブが増えているかで判断します。タブ自体が出ない場合は拡張がそのページに注入されていないため、インストール後にページを再読み込みします。タブは出るのに中身が空という症状は原因が別にあるので、後述の切り分け節を参照してください。

### configureStoreでの既定有効とcreateStore使用時の合成

アプリ側の設定は、`configureStore` を使っているなら不要です。RTK公式APIドキュメントは `devTools` オプションについて「configureStoreがRedux DevToolsブラウザ拡張のサポートを自動的に有効にすべきかどうかを示す。既定値は true」と記載しています。古い記事にある `window.__REDUX_DEVTOOLS_EXTENSION__` の手動合成は不要です。

一方、既存プロジェクトが `createStore` でストアを組んでいる場合は、エンハンサーを自分で合成する必要があります。`createStore` は Redux 4.2.0 で `@deprecated` マークが付きましたが、公式は「無期限に動作し続け、削除されることは決してない」「この非推奨化は純粋に視覚的な指標である」と明言しており、既存コードが動かなくなるわけではありません。打ち消し線の表示だけを消したいなら `import { legacy_createStore as createStore } from 'redux'` に書き換えられます。DevToolsを繋ぐ場合の合成は次の形です。

```
import { legacy_createStore as createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from '@redux-devtools/extension'
import { thunk } from 'redux-thunk'
import rootReducer from './reducers'

const store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(thunk))
)
```

`composeWithDevTools` は `@redux-devtools/extension` パッケージの追加インストールが必要です。新規に書くなら `configureStore` を選び、この合成そのものを不要にするほうが早いでしょう。

## Redux DevToolsの画面の読み方｜Action・State・Diff・Trace

### 3タブの使い分けと不具合調査の入口

Reduxタブの左ペインには、ディスパッチされたアクションが発生順に積まれます。右ペインで主に使う表示は3つです。

| タブ     | 表示内容               | 主な用途             |
| ------ | ------------------ | ---------------- |
| Action | アクションのtypeとpayload | 意図した値が送られているかの確認 |
| State  | そのアクション直後のストア全体    | 更新後の状態の検証        |
| Diff   | 直前の状態からの差分のみ       | 意図しない箇所の書き換え検出   |

調査の入口はDiffです。「更新したはずの値が変わらない」なら、アクションは届いているのにリデューサーが該当ケースを処理していない可能性が高いと判断できます。「関係ない値まで変わる」なら、別スライスが同じアクションtypeに反応している疑いが濃厚です。Stateタブの全量表示から目視で探すより、差分のほうが圧倒的に速く原因へ届きます。

### タイムトラベルとSkipによる原因の切り分け

アクション一覧の各行にある**Jump**を押すと、その時点のステートがアプリへ再適用され、画面が過去の状態へ巻き戻ります。これがタイムトラベルです。**Skip**は特定のアクションだけを無かったことにして以降を再計算するため、「このディスパッチが原因か」の切り分けに使えます。

使いどころは、フォーム入力を10ステップ進めた先でしか出ない不具合のような、再現コストの高いケースです。手順を再現し直さずに、問題のアクションだけを抜いた結果を確認できます。Redux公式FAQが採用理由として挙げる「状態が時間とともにどう更新されたかを見る必要がある」は、実質この機能を指しています。

### traceとmaxAgeによる履歴の調整

既定の設定では追いきれない場面が2つあります。1つは「このアクションはどこから送られたのか」がわからないケース。Traceタブにスタックトレースを出すには、`trace` オプションを明示的に有効にする必要があります。既定値は false で、保存されるスタックフレーム数は `traceLimit` の既定 10 です。

```
export const store = configureStore({
  reducer: rootReducer,
  devTools: {
    trace: true,
    traceLimit: 25,
    maxAge: 100,
  },
})
```

もう1つは、履歴が途中で切れるケースです。DevToolsが保持するアクション数は `maxAge` の既定 50 件で、これを超えると古いものから捨てられます。ポーリングやマウス移動でアクションが多発するアプリでは、調査したい時点の履歴が既に消えていることがあります。上記のように `maxAge` を引き上げてください。ただし履歴は全てメモリ上に保持されるため、極端な値は開発環境の動作を重くします。

## Redux DevToolsが動かないときの切り分け

### Reduxタブが無反応な場合の確認順序

Reduxタブは出るのにアクションが1件も流れてこない場合、次の順で疑うと早く終わります。

- ストアを `createStore` で生成していて、DevTools用エンハンサーを合成していない（`configureStore` なら既定で接続済み）
- 本番ビルドを見ている。`devTools: process.env.NODE_ENV !== 'production'` のように環境で切っている設定では、本番モードのビルドに接続されない
- ストアを複数生成している。テスト用や旧実装のストアが残っていると、拡張が別インスタンスを掴む
- `Provider` に渡したストアと、DevToolsへ接続したストアが別物になっている

本番環境で有効にしたままにする運用は避けてください。ストアの全内容が閲覧可能になり、認証トークンや個人情報を保持している場合は情報漏洩に直結します。

### ステートが欠ける非シリアライズ値の問題

アクションは表示されるのにStateタブが空、あるいは一部のキーだけ表示されないという症状は、ストア設定ではなくステートの中身が原因です。`Map`、`Set`、`Date` オブジェクト、クラスインスタンス、関数といった直列化できない値が入ると、DevToolsはそれらを正しく表示できません。

開発中であれば、`configureStore` が組み込む serializableStateInvariant ミドルウェアが「A non-serializable value was detected in the state」という警告で先に教えてくれます。この警告は作法の指摘にとどまりません。直列化できない値はステートの永続化（localStorageへの保存）やSSRのハイドレーションも壊すため、値の側を直すのが本筋です。日時はISO 8601文字列かUnixタイムスタンプで持ち、`Map` はプレーンオブジェクトへ変換してから格納します。

ファイルアップロードで `File` オブジェクトを一時的にアクションへ載せるなど、どうしても通す必要がある場合は、該当のアクションtypeだけを除外します。

```
export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['upload/fileSelected'],
      },
    }),
})
```

`serializableCheck: false` でチェック全体を無効化する指定もありますが、以後すべての混入を見逃します。範囲を絞った除外に留めるべきです。

## 接続まわりで頻出するエラーの対処

### ステート直接変更エラーとImmerの適用範囲

「A state mutation was detected」は、`configureStore` が開発時に組み込む immutableStateInvariant ミドルウェアが出すエラーです。開発時の既定ミドルウェアは actionCreatorInvariant・immutableStateInvariant・thunk・serializableStateInvariant の4種で、本番ビルドでは thunk のみが残ります。実行時チェックが本番性能に影響しない設計です。

`createSlice` の中では `state.value += 1` と書けるのに、なぜこのエラーが出るのか。境界はImmerのドラフトが有効な範囲にあります。Immerが書き換え記法を不変更新へ変換するのは、`createSlice` や `createReducer` のリデューサー関数の内部だけです。`useSelector` が返したオブジェクトへコンポーネント内で直接代入した場合や、リデューサーから受け取ったステートの一部を非同期に書き換えた場合は、ドラフトの外側での変更となりエラーになります。

もう1つの典型が、リデューサー内でドラフトを変更しつつ、同時に新しいステートを `return` するパターンです。この場合はImmerが「An immer producer returned a new value and modified its draft」という例外を投げます。ドラフトを書き換えるか、新しいオブジェクトを返すか、リデューサー1つにつきどちらか一方へ統一してください。

### useSelectorで再レンダリングが止まらないケース

DevToolsのアクション履歴は正常なのに画面が過剰に再描画される場合、原因はストアではなくセレクタ側にあります。前述の「毎回新しいオブジェクトを返す」パターンのほか、セレクタ内で `filter` や `map` を呼んで新しい配列を生成しているケースが典型です。配列は毎回別参照になるため、中身が同じでも再レンダリングが走ります。

切り分けにはDevToolsのDiffが使えます。アクションごとのDiffが空、つまりステートが実際には変わっていないのにコンポーネントが再描画されているなら、原因はセレクタの参照同一性です。この場合はreselectでメモ化するか、必要なプリミティブ値だけを選択する形へ書き換えます。フォームのように値の更新頻度が高い状態は、そもそもReduxへ載せず[React Hook Formとは？使い方・バリデーション・v7の書き方を実例で解説](/tech/details/2898/)のようなフォーム専用ライブラリでコンポーネント内に閉じるほうが、再レンダリングの問題を根本から避けられます。

## よくある質問

### Redux DevToolsに何も表示されないのはなぜですか？

最初に疑うのはストアの生成方法です。`configureStore` なら `devTools` の既定値が true のため設定不要ですが、`createStore` を直接使っている場合は `composeWithDevTools` によるエンハンサーの合成が別途必要になります。次に本番ビルドを見ていないか、テスト用など複数のストアを生成していないかを確認します。アクションは流れるのにステートが空な場合は、ステート内の非シリアライズ値（`Map`、クラスインスタンス等）が原因です。

### createStoreで作ったストアをDevToolsに繋ぐにはどうしますか？

`@redux-devtools/extension` をインストールし、`composeWithDevTools(applyMiddleware(...))` を `createStore` の第2引数に渡します。`createStore` 自体は Redux 4.2.0 で非推奨マークが付いていますが、公式が「削除されることは決してない」と明言しているため、既存コードを急いで書き換える必要はありません。打ち消し線の表示だけを消すなら `legacy_createStore` へのエイリアスインポートが使えます。

### react-reduxはReact 17のプロジェクトで使えますか？

react-redux 9 系は使えません。9.3.0 の peerDependencies は `react: ^18.0 || ^19` で、公式移行ガイドも「React-Redux v9はReact 18を必須とし、React 16や17をサポートしない」と記載しています。npm 7 以降ではインストールが `ERESOLVE` で失敗するため、React 17以前のままなら react-redux 8 系をバージョン指定でインストールしてください。

### useSelectorを呼ぶたびに再レンダリングが起きるのはなぜですか？

セレクタが毎回新しい参照を返しているためです。react-reduxは戻り値を参照等価で比較するので、オブジェクトリテラルや `filter` の結果をそのまま返すと、中身が同じでも「変わった」と判定されます。プリミティブ値ごとに `useSelector` を分けるか、reselectでメモ化したセレクタを使ってください。

### Redux DevToolsは本番環境でも有効にしてよいですか？

推奨しません。ストアの全内容が閲覧可能になるため、認証トークンやユーザーの個人情報を保持している場合は情報漏洩の経路になります。`devTools: process.env.NODE_ENV !== 'production'` のように環境変数で切り替えるのが一般的な運用です。

## 関連記事

- [Redux Toolkitとは？createSliceとRTK Queryの実装と採用判断【2026年8月版】](/tech/details/16013/)
- [Reactフックの基本と導入：React 16.8で追加された新機能の概要](/tech/details/3671/)
- [ReactのuseRefの使い方｜useStateとの違い・DOM参照・値の保持を実例で解説](/tech/details/3279/)
- [React Compilerとは？自動メモ化の仕組みと導入方法・useMemoとの違いを解説](/tech/details/9415/)
- [React Hook Formとは？使い方・バリデーション・v7の書き方を実例で解説](/tech/details/2898/)

---

出典: [Redux DevToolsの使い方｜React-Reduxの接続とステートの追跡](<https://www.issoh.co.jp/tech/details/3060/>)（株式会社一創）
