Reduxエッセンシャルズ Part 3: 基本的なReduxデータフロー
このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
- ReactアプリケーションでのReduxストアのセットアップ方法
createSliceによるリデューサーロジックの「スライス」追加方法useSelectorフックを使ったコンポーネントでのReduxデータ読み取りuseDispatchフックを使ったコンポーネントでのアクション発行方法
- 「アクション」「リデューサー」「ストア」「ディスパッチ」といったReduxの主要概念の理解(詳細はPart 1: Reduxの概要と概念参照)
- TypeScriptの基本構文と使用方法の理解
はじめに
Part 1: Reduxの概要と概念では、Reduxがグローバルなアプリ状態を一元管理することでメンテナンス性の高いアプリ構築を可能にする仕組みを解説しました。またアクションオブジェクトのディスパッチ、新しい状態値を返すリデューサー関数、非同期ロジックを扱うthunkといったReduxの核心概念についても扱いました。Part 2: Redux Toolkitのアプリ構造では、Redux ToolkitのconfigureStoreやcreateSlice、React-ReduxのProviderやuseSelectorといったAPIが連携してReduxロジックを記述し、Reactコンポーネントから操作する方法を学びました。
これらの概念を理解したところで、知識を実践に移す時が来ました。実際のユースケースを示す機能を備えた小さなソーシャルメディアフィードアプリを構築します。これにより皆さん自身のアプリケーションでReduxを活用する方法を理解できるでしょう。
コード記述にはTypeScript構文を使用します。ReduxはプレーンJavaScriptでも使用可能ですが、TypeScriptは一般的なミスを防ぎ、コードの組み込みドキュメントとして機能し、エディタがReactコンポーネントやReduxリデューサーで必要な変数型を提示してくれます。ReduxアプリケーションではTypeScriptの使用を強く推奨します。
このサンプルアプリは完成度の高いプロダクション対応プロジェクトではありません。目的はRedux APIの使用方法や典型的なパターンを学び、限定的な例を通じて適切な方向性を示すことです。初期に構築したコンポーネントの一部は、より良い方法を示すために後で更新されます。すべての概念が実際に使用される様子を確認するため、チュートリアル全体を読破してください。
プロジェクト設定
このチュートリアルでは、ReactとReduxが事前設定され、デフォルトのスタイリングが適用され、実際のAPIリクエストを記述できる模擬REST APIを備えたスタータープロジェクトを準備しました。実際のアプリケーションコード作成の基盤としてこれを活用します。
開始するには、こちらのCodeSandboxを開いてフォークしてください:
GitHubリポジトリから同じプロジェクトをクローンすることも可能です。このプロジェクトはパッケージマネージャーとしてYarn 4を使用するよう設定されていますが、任意のパッケージマネージャー(NPM, PNPM, Bun)が使用可能です。パッケージインストール後、yarn devコマンドでローカル開発サーバーを起動できます。
構築予定の完成版を確認したい場合は、tutorial-steps-tsブランチをチェックするか、こちらのCodeSandboxで最終版を参照してください。
このページの例は、Tania Rascia の Using Redux with React チュートリアルに触発されました。また、スタイリングには彼女の Primitive UI CSS starter を使用しています。
新しい Redux + React プロジェクトの作成
このチュートリアルを終えたら、おそらく自身のプロジェクトで試してみたくなるでしょう。最も速く新しい Redux + React プロジェクトを作成する方法として、Vite および Next.js 向けの Redux テンプレートを使用することをお勧めします。これらのテンプレートには Redux Toolkit と React-Redux が既に設定されており、Part 1 でご覧になった「カウンター」アプリの例が使われています。これにより、Redux パッケージを追加したりストアを設定したりする必要なく、実際のアプリケーションコードの作成にすぐに取りかかることができます。
初期プロジェクトの確認
初期プロジェクトに含まれているものを簡単に見てみましょう:
-
/public: 基本となる CSS スタイルやアイコンなどの静的ファイル -
/srcmain.tsx: アプリケーションのエントリポイントファイル。<App>コンポーネントをレンダリングします。この例では、ページロード時にフェイクの REST API を設定します。App.tsx: メインアプリケーションコンポーネント。トップのナビゲーションバーをレンダリングし、他のコンテンツのクライアントサイドルーティングを処理します。index.css: アプリケーション全体のスタイル/apiclient.ts: HTTP GET および POST リクエストを行うための小さなfetchラッパークライアントserver.ts: データ用のフェイク REST API を提供します。後ほど、アプリはこれらのフェイクエンドポイントからデータを取得します。
/appNavbar.tsx: トップのヘッダーとナビゲーションコンテンツをレンダリングします
今アプリを読み込むと、ヘッダーとウェルカムメッセージが表示されますが、機能はまだありません。
では、始めましょう!
Redux ストアの設定
現在、プロジェクトは空です。そのため、まず Redux 関連のワンタイムセットアップを行う必要があります。
Redux パッケージの追加
package.json を見ると、Redux を使用するために必要な次の2つのパッケージが既にインストールされていることがわかります:
-
@reduxjs/toolkit: モダンな Redux パッケージ。アプリ構築に使用するすべての Redux 機能が含まれています -
react-redux: React コンポーネントが Redux ストアと通信するために必要な機能
スクラッチからプロジェクトを設定する場合は、これらのパッケージを自身でプロジェクトに追加することから始めてください。
ストアの作成
最初のステップは、実際の Redux ストアを作成することです。Redux の原則の1つは、アプリケーション全体で 1つ のストアインスタンスしか存在してはいけないことです。
通常、Redux ストアインスタンスは専用のファイルで作成してエクスポートします。アプリケーションの実際のフォルダ構造は任意ですが、アプリケーション全体のセットアップと設定は src/app/ フォルダに置くのが標準的です。
src/app/store.ts ファイルを追加し、ストアを作成することから始めます。
Redux Toolkit には configureStore というメソッドが含まれています。この関数は新しい Redux ストアインスタンスを作成します。ストアの動作を変更するために渡せるオプションがいくつかあります。また、典型的なミスのチェックや、状態の内容とアクションの履歴を確認できるように Redux DevTools 拡張を有効にするなど、最も一般的で有用な設定を自動的に適用します。
import { configureStore } from '@reduxjs/toolkit'
import type { Action } from '@reduxjs/toolkit'
interface CounterState {
value: number
}
// An example slice reducer function that shows how a Redux reducer works inside.
// We'll replace this soon with real app logic.
function counterReducer(state: CounterState = { value: 0 }, action: Action) {
switch (action.type) {
// Handle actions here
default: {
return state
}
}
}
export const store = configureStore({
// Pass in the root reducer setup as the `reducer` argument
reducer: {
// Declare that `state.counter` will be updated by the `counterReducer` function
counter: counterReducer
}
})
configureStore には常に reducer オプションが必要です。これは通常、アプリケーションの異なる部分に対する個々の「スライスリデューサー」を含むオブジェクトであるべきです。(必要に応じて、ルートリデューサー関数を別途作成し、reducer 引数として渡すこともできます。)
最初のステップでは、設定の見た目を確認するため、counterスライスのモックスライスリデューサー関数を渡しています。実際に構築するアプリ用の本物のスライスリデューサーとは、すぐに置き換える予定です。
Next.jsを使用している場合、セットアッププロセスには追加の手順が必要です。Next.jsでReduxをセットアップする方法の詳細については、Next.jsでのセットアップページを参照してください。
ストアの提供
Redux自体はプレーンなJavaScriptライブラリであり、あらゆるUIレイヤーで動作します。このアプリではReactを使用しているため、ReactコンポーネントがReduxストアとやり取りできるようにする方法が必要です。
これを実現するには、React-Reduxライブラリを使用し、Reduxストアを<Provider>コンポーネントに渡す必要があります。これにより、ReactのContext APIを利用して、アプリケーション内のすべてのReactコンポーネントがReduxストアにアクセスできるようになります。
他のアプリケーションコードファイルでReduxストアを直接インポートしようとしないことが重要です!ストアファイルは1つしかないため、直接インポートすると循環インポート問題(ファイルAがBを、BがCを、CがAをインポートするなど)が発生し、追跡困難なバグの原因となります。さらに、コンポーネントとReduxロジックのテストを可能にする必要があり、これらのテストでは独自のReduxストアインスタンスを作成する必要があります。Contextを介したストアの提供により、柔軟性が保たれ、インポート問題も回避できます。
これを行うには、storeをエントリポイントファイルmain.tsxにインポートし、<App>コンポーネントを<Provider>でラップしてストアを渡します:
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import App from './App'
import { store } from './app/store'
// skip mock API setup
const root = createRoot(document.getElementById('root')!)
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)
Reduxステートの確認
ストアが用意できたので、Redux DevTools拡張機能を使用して現在のReduxステートを表示できます。
ブラウザのDevToolsビュー(ページ上で右クリックして「Inspect」を選択するなど)を開き、「Redux」タブをクリックすると、ディスパッチされたアクションの履歴と現在のステート値が表示されます:

現在のステート値は次のようなオブジェクトになるはずです:
{
counter: {
value: 0
}
}
この形状はconfigureStoreに渡したreducerオプションで定義されています:counterという名前のフィールドを持つオブジェクトで、counterフィールドのスライスリデューサーはステートとして{value}のようなオブジェクトを返します。
ストアの型のエクスポート
TypeScriptを使用しているため、「Reduxステートの型」と「Reduxストアのdispatch関数の型」を頻繁に参照することになります。
これらの型をstore.tsファイルからエクスポートする必要があります。TSのtypeof演算子を使用して、Reduxストアの定義に基づいて型を推論させます:
import { configureStore } from '@reduxjs/toolkit'
// omit counter slice setup
export const store = configureStore({
reducer: {
counter: counterReducer
}
})
// Infer the type of `store`
export type AppStore = typeof store
// Infer the `AppDispatch` type from the store itself
export type AppDispatch = typeof store.dispatch
// Same for the `RootState` type
export type RootState = ReturnType<typeof store.getState>
エディターでRootState型にカーソルを合わせると、type RootState = { counter: CounterState; }と表示されるはずです。この型はストア定義から自動的に導出されるため、reducer設定への将来の変更はすべて自動的にRootState型にも反映されます。これにより、一度定義するだけで常に正確な型が保証されます。
型付きフックのエクスポート
コンポーネントではReact-ReduxのuseSelectorおよびuseDispatchフックを頻繁に使用します。これらのフックを使用するたびにRootStateとAppDispatchの型を参照する必要があります。
正しい型が組み込まれた「事前型付け(pre-typed)」バージョンのフックを設定すれば、使用を簡素化し、型の繰り返しを避けることができます。
React-Redux 9.1には、フックに正しい型を適用するための.withTypes()メソッドが含まれています。これらの事前に型付けされたフックをエクスポートし、アプリケーション全体で使用できます:
// This file serves as a central hub for re-exporting pre-typed Redux hooks.
import { useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
これでセットアッププロセスは完了です。アプリの構築を始めましょう!
メイン投稿フィード
ソーシャルメディアフィードアプリのメイン機能は投稿リストです。この機能には後でさらに要素を追加しますが、最初の目標は画面に投稿エントリのリストを表示することだけです。
投稿スライスの作成
最初のステップは、投稿データを保持する新しいReduxの「スライス」を作成することです。
「スライス」は、アプリケーションの単一機能に関する Redux リデューサーとアクションの集合体であり、通常は1つのファイル内で定義されます。この名称は、ルートの Redux ステートオブジェクトを複数の状態「スライス」に分割することに由来しています。
Reduxストアに投稿データが格納されたら、そのデータをページに表示するReactコンポーネントを作成できます。
src内に新しいfeaturesフォルダを作成し、そのfeaturesの中にpostsフォルダを置き、postsSlice.tsという名前の新しいファイルを追加してください。
Redux ToolkitのcreateSlice関数を使用して、投稿データを処理するreducer関数を作成します。Reducer関数には初期データが必要です。これにより、アプリ起動時にReduxストアに値が読み込まれます。
まずはUI作成を始められるよう、ダミーの投稿オブジェクトを含む配列を作成します。
createSliceをインポートし、初期投稿配列を定義してcreateSliceに渡し、createSliceによって生成された投稿reducer関数をエクスポートします:
import { createSlice } from '@reduxjs/toolkit'
// Define a TS type for the data we'll be using
export interface Post {
id: string
title: string
content: string
}
// Create an initial state value for the reducer, with that type
const initialState: Post[] = [
{ id: '1', title: 'First Post!', content: 'Hello!' },
{ id: '2', title: 'Second Post', content: 'More text' }
]
// Create the slice and pass in the initial state
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {}
})
// Export the generated reducer function
export default postsSlice.reducer
新しいスライスを作成するたびに、その reducer 関数を Redux ストアに追加する必要があります。すでに Redux ストアは作成されていますが、現時点では内部にデータがありません。app/store.ts を開き、postsReducer 関数をインポートして、counter 関連のコードをすべて削除し、configureStore の呼び出しを更新して、postsReducer を posts という名前の reducer フィールドとして渡すようにしてください:
import { configureStore } from '@reduxjs/toolkit'
// Removed the `counterReducer` function, `CounterState` type, and `Action` import
import postsReducer from '@/features/posts/postsSlice'
export const store = configureStore({
reducer: {
posts: postsReducer
}
})
これにより、トップレベルstateオブジェクトにpostsフィールドが作成され、state.postsの全データはactionがdispatchされるたびにpostsReducer関数によって更新されます。
Redux DevTools Extensionを開いて現在のstate内容を確認することで、動作を確認できます:

投稿リストの表示
ストアに投稿データが格納されたので、投稿リストを表示するReactコンポーネントを作成します。フィード投稿機能に関連するコードはすべてpostsフォルダに置くべきです。PostsList.tsxという新しいファイルを作成してください(TypeScriptで記述されJSX構文を使用するReactコンポーネントのため、正しくコンパイルするには.tsx拡張子が必要です)。
投稿リストをレンダリングするにはデータ取得が必要です。ReactコンポーネントはReact-ReduxライブラリのuseSelectorフックを使用してReduxストアからデータを読み取れます。作成する「セレクタ関数」はRedux stateオブジェクト全体をパラメータとして受け取り、コンポーネントが必要な特定のデータを返します。
TypeScriptを使用しているため、全コンポーネントでsrc/app/hooks.tsに追加した事前型付け済みのuseAppSelectorフックを使用する必要があります。これには適切なRootState型が既に含まれています。
最初のPostsListコンポーネントはReduxストアからstate.postsの値を読み取り、投稿配列をループ処理して各投稿を画面に表示します:
import { useAppSelector } from '@/app/hooks'
export const PostsList = () => {
// Select the `state.posts` value from the store into the component
const posts = useAppSelector(state => state.posts)
const renderedPosts = posts.map(post => (
<article className="post-excerpt" key={post.id}>
<h3>{post.title}</h3>
<p className="post-content">{post.content.substring(0, 100)}</p>
</article>
))
return (
<section className="posts-list">
<h2>Posts</h2>
{renderedPosts}
</section>
)
}
次に、App.tsxのルーティングを更新し、「ようこそ」メッセージの代わりにPostsListコンポーネントを表示するようにします。PostsListコンポーネントをApp.tsxにインポートし、ようこそテキストを<PostsList />に置き換えます。また、React Fragmentでラップします。なぜなら、まもなくメインページに別の要素を追加するからです。
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import { Navbar } from './components/Navbar'
import { PostsList } from './features/posts/PostsList'
function App() {
return (
<Router>
<Navbar />
<div className="App">
<Routes>
<Route
path="/"
element={
<>
<PostsList />
</>
}
></Route>
</Routes>
</div>
</Router>
)
}
export default App
追加すると、アプリのメインページは次のようになります。

進捗です!Reduxストアにデータを追加し、Reactコンポーネントで画面に表示できました。
新規投稿の追加
他のユーザーが書いた投稿を見るのも良いですが、自分自身の投稿を書きたいところです。投稿を書いて保存できる「新規投稿」フォームを作成しましょう。
まずは空のフォームを作成してページに追加します。その後、フォームをReduxストアに接続し、「投稿を保存」ボタンをクリックしたときに新しい投稿が追加されるようにします。
新規投稿フォームの追加
postsフォルダにAddPostForm.tsxを作成します。投稿のタイトル用のテキスト入力欄と、本文用のテキストエリアを追加します。
import React from 'react'
// TS types for the input fields
// See: https://epicreact.dev/how-to-type-a-react-form-on-submit-handler/
interface AddPostFormFields extends HTMLFormControlsCollection {
postTitle: HTMLInputElement
postContent: HTMLTextAreaElement
}
interface AddPostFormElements extends HTMLFormElement {
readonly elements: AddPostFormFields
}
export const AddPostForm = () => {
const handleSubmit = (e: React.FormEvent<AddPostFormElements>) => {
// Prevent server submission
e.preventDefault()
const { elements } = e.currentTarget
const title = elements.postTitle.value
const content = elements.postContent.value
console.log('Values: ', { title, content })
e.currentTarget.reset()
}
return (
<section>
<h2>Add a New Post</h2>
<form onSubmit={handleSubmit}>
<label htmlFor="postTitle">Post Title:</label>
<input type="text" id="postTitle" defaultValue="" required />
<label htmlFor="postContent">Content:</label>
<textarea
id="postContent"
name="postContent"
defaultValue=""
required
/>
<button>Save Post</button>
</form>
</section>
)
}
なお、ここではまだRedux固有のロジックは含まれていません。次にそれを追加します。
この例では、"非制御"入力を使用し、HTML5フォーム検証で空の入力フィールドの送信を防いでいます。ただし、フォームから値を読み取る方法は任意です。これはReactの使用パターンに関する好みであり、Redux固有のものではありません。
そのコンポーネントをApp.tsxにインポートし、<PostsList />コンポーネントのすぐ上に追加します。
// omit outer `<App>` definition
<Route
path="/"
element={
<>
<AddPostForm />
<PostsList />
</>
}
></Route>
ヘッダーのすぐ下にフォームが表示されるはずです。
投稿エントリの保存
では、Reduxストアに新しい投稿エントリを追加するために、postsスライスを更新しましょう。
postsスライスは、投稿データに対するすべての更新を処理する役割を持っています。createSlice呼び出しの中には、reducersというオブジェクトがあります。現在は空です。ここに、投稿が追加されるケースを処理するためのリデューサー関数を追加する必要があります。
reducersの中に、postAddedという名前の関数を追加します。この関数は2つの引数を受け取ります:現在のstate値と、ディスパッチされたactionオブジェクトです。postsスライスは、自身が担当するデータのみを知っているため、state引数は投稿の配列自体であり、Reduxステートオブジェクト全体ではありません。
actionオブジェクトには、新しい投稿エントリがaction.payloadフィールドとして含まれます。リデューサー関数を宣言する際には、TypeScriptにaction.payloadの実際の型が何であるかを伝える必要があります。これにより、引数を渡すときやaction.payloadの内容にアクセスするときに正しくチェックできます。そのためには、Redux ToolkitからPayloadAction型をインポートし、action引数をaction: PayloadAction<ThePayloadTypeHere>として宣言します。この場合、action: PayloadAction<Post>となります。
実際のステート更新は、新しい投稿オブジェクトをstate配列に追加することです。これは、リデューサー内でstate.push()を使って行えます。
覚えておいてください: Reduxのリデューサー関数は、常にコピーを作成してイミュータブルに新しいステート値を生成しなければなりません! createSlice()内では、Array.push()のような変異関数を呼び出したり、state.someField = someValueのようにオブジェクトフィールドを変更したりしても安全です。なぜなら、Immerライブラリを使用して内部的にそれらの変異を安全なイミュータブルな更新に変換するからです。ただし、createSliceの外でデータを変異させようとしないでください!
postAdded リデューサー関数を記述する際、createSlice は自動的に同じ名前の"アクションクリエーター"関数を生成します。このアクションクリエーターをエクスポートし、ユーザーが「投稿を保存」をクリックした時にアクションをディスパッチするためにUIコンポーネント内で使用できます。
// Import the `PayloadAction` TS type
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
// omit initial state
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
// Declare a "case reducer" named `postAdded`.
// The type of `action.payload` will be a `Post` object.
postAdded(state, action: PayloadAction<Post>) {
// "Mutate" the existing state array, which is
// safe to do here because `createSlice` uses Immer inside.
state.push(action.payload)
}
}
})
// Export the auto-generated action creator with the same name
export const { postAdded } = postsSlice.actions
export default postsSlice.reducer
用語的には、ここでのpostAddedは**"ケースリデューサー"**の一例です。これはスライス内に存在するリデューサー関数で、ディスパッチされた特定のアクションタイプを処理します。概念的には、switch文内のcase節を記述するようなものです - 「この特定のアクションタイプが確認されたら、このロジックを実行する」という考え方です:
function sliceReducer(state = initialState, action) {
switch (action.type) {
case 'posts/postAdded': {
// update logic here
}
}
}
「投稿追加」アクションのディスパッチ
AddPostFormにはテキスト入力欄と送信ハンドラーをトリガーする「投稿を保存」ボタンがありますが、現時点ではボタンは機能しません。送信ハンドラーを更新し、ユーザーが入力したタイトルと内容を含む新しい投稿オブジェクトを渡してpostAddedアクションクリエーターをディスパッチする必要があります。
投稿オブジェクトにはidフィールドも必要です。現在、初期テスト投稿ではIDにダミーの数値を使用しています。次の増分ID番号を計算するコードを書くことも可能ですが、ランダムなユニークIDを生成する方が良いでしょう。Redux Toolkitにはこのために使用できるnanoid関数があります。
IDの生成方法とアクションのディスパッチについてはパート4: Reduxデータの使用で詳しく説明します。
コンポーネントからアクションをディスパッチするにはストアのdispatch関数にアクセスする必要があります。これはReact-ReduxのuseDispatchフックを呼び出すことで取得します。TypeScriptを使用しているため、正しい型を持つuseAppDispatchフックをインポートする必要があります。また、このファイルにpostAddedアクションクリエーターをインポートする必要があります。
コンポーネント内でdispatch関数を利用できるようになったら、クリックハンドラー内でdispatch(postAdded())を呼び出せます。フォームからタイトルと内容の値を取得し、新しいIDを生成して、これらを組み合わせた新しい投稿オブジェクトをpostAdded()に渡します。
import React from 'react'
import { nanoid } from '@reduxjs/toolkit'
import { useAppDispatch } from '@/app/hooks'
import { type Post, postAdded } from './postsSlice'
// omit form types
export const AddPostForm = () => {
// Get the `dispatch` method from the store
const dispatch = useAppDispatch()
const handleSubmit = (e: React.FormEvent<AddPostFormElements>) => {
// Prevent server submission
e.preventDefault()
const { elements } = e.currentTarget
const title = elements.postTitle.value
const content = elements.postContent.value
// Create the post object and dispatch the `postAdded` action
const newPost: Post = {
id: nanoid(),
title,
content
}
dispatch(postAdded(newPost))
e.currentTarget.reset()
}
return (
<section>
<h2>Add a New Post</h2>
<form onSubmit={handleSubmit}>
<label htmlFor="postTitle">Post Title:</label>
<input type="text" id="postTitle" defaultValue="" required />
<label htmlFor="postContent">Content:</label>
<textarea
id="postContent"
name="postContent"
defaultValue=""
required
/>
<button>Save Post</button>
</form>
</section>
)
}
では、タイトルとテキストを入力して「投稿を保存」をクリックしてみてください。投稿リストにその投稿の新しい項目が表示されるはずです。
おめでとうございます!初めての機能するReact + Reduxアプリを構築しました!
これでReduxデータフローの完全なサイクルが示されました:
-
投稿リストは
useSelectorでストアから初期投稿セットを読み取り、初期UIをレンダリング -
新しい投稿エントリのデータを含む
postAddedアクションをディスパッチ -
投稿リデューサーが
postAddedアクションを検知し、新しいエントリで投稿配列を更新 -
ReduxストアがUIにデータ変更を通知
-
投稿リストが更新された投稿配列を読み取り、新しい投稿を表示するために自身を再レンダリング
これ以降に追加する新機能はすべて、ここで見た基本的なパターンに従います:状態スライスの追加、リデューサー関数の記述、アクションのディスパッチ、Reduxストアからのデータに基づくUIのレンダリングです。
Redux DevTools Extensionでディスパッチしたアクションを確認し、そのアクションに応じてRedux状態がどのように更新されたかを確認できます。アクションリストで"posts/postAdded"エントリをクリックすると、"Action"タブは次のように表示されます:

"Diff"タブには、state.postsにインデックス2として新しい項目が1つ追加されたことが表示されるはずです。
Reduxストアにはアプリケーション全体で「グローバル」と見なされるデータのみ含めるべきです! 今回の場合、入力フィールドの最新値が必要なのはAddPostFormコンポーネントだけです。「制御された」入力を使用してフォームを構築したとしても、一時データをReduxストアに保持しようとするのではなく、Reactのコンポーネント状態に保持するべきです。ユーザーがフォームの入力を完了したら、Reduxアクションをディスパッチして、ユーザー入力に基づいた最終値をストアに反映します。
学んだこと
Reduxアプリの基礎 - ストア、リデューサーを含むスライス、アクションをディスパッチするUIを設定しました。現時点でのアプリの外観は次のとおりです:
このセクションで学んだ内容をまとめましょう:
- Reduxアプリには単一の
storeがあり、<Provider>コンポーネント経由でReactコンポーネントに渡される - Reduxの状態は「リデューサー関数」によって更新される:
- リデューサーは既存の状態値をコピーし、新しいデータで変更を加えることで、常に_イミュータブル_に新しい状態を計算する
- Redux Toolkitの
createSlice関数は「スライスリデューサー」関数を生成し、安全なイミュータブルな更新に変換される「変更」コードを記述できる - 生成されたスライスリデューサー関数は
configureStoreのreducerフィールドに追加され、Reduxストア内のデータ構造と状態フィールド名を定義する
- Reactコンポーネントは
useSelectorフックを使用してストアからデータを読み取る- セレクター関数は
stateオブジェクト全体を受け取り、値を返す必要がある - セレクターはReduxストアが更新されるたびに再実行され、返されるデータが変更された場合にコンポーネントが再レンダリングされる
- セレクター関数は
- Reactコンポーネントは
useDispatchフックを使用してアクションをディスパッチしストアを更新するcreateSliceはスライスに追加された各リデューサーに対応するアクションクリエーター関数を生成する- コンポーネント内で
dispatch(someActionCreator())を呼び出してアクションをディスパッチする - リデューサーが実行され、該当アクションかどうかを確認し、適切な場合は新しい状態を返す
- フォーム入力値などの一時データはReactコンポーネントの状態またはプレーンなHTML入力フィールドとして保持する。ユーザーがフォーム操作を完了した時にReduxアクションをディスパッチしてストアを更新する
- TypeScriptを使用する場合、初期設定でstoreに基づいた
RootStateとAppDispatchのTS型を定義し、React-ReduxのuseSelectorとuseDispatchフックの事前型付きバージョンをエクスポートする必要がある
次のステップ
これでReduxの基本的なデータフローを理解したので、Part 4: Reduxデータの使用に進みましょう。追加機能を実装し、ストアに既にあるデータを操作する方法の実例を確認します。