メインコンテンツにスキップ

Reduxの基礎, パート4: ストア

Reduxの基礎, パート4: ストア

学習内容
  • Reduxストアを作成する方法
  • ストアを使用して状態を更新し、更新をリッスンする方法
  • ストアの機能を拡張するように構成する方法
  • Redux DevTools拡張機能を設定してアプリをデバッグする方法

はじめに

パート3: ステート、アクション、およびリデューサーでは、ToDoアプリの例の作成を開始しました。ビジネス要件をリストし、アプリを機能させるために必要なステート構造を定義し、ユーザーがアプリを操作する際に発生する可能性のあるイベントの種類と一致する「何が起こったか」を記述する一連のアクションタイプを作成しました。また、state.todosおよびstate.filtersセクションの更新を処理できるリデューサー関数を作成し、ReduxのcombineReducers関数を使用して、アプリの各機能の異なる「スライスリデューサー」に基づいて「ルートリデューサー」を作成する方法を確認しました。

さて、Reduxアプリの中心的な要素であるストアを使用して、これらの要素をまとめましょう。

注意

このチュートリアルでは、Reduxの背後にある原則と概念を説明するために、今日Reduxでアプリを構築する際の適切なアプローチとして教えているRedux Toolkitを使用した「モダンRedux」パターンよりも多くのコードを必要とする、古いスタイルのReduxロジックパターンを意図的に示しています。これは、本番環境で使用できるプロジェクトとして想定されていません。

Redux Toolkitを使用した「モダンRedux」の使用方法については、以下のページを参照してください

Reduxストア

Reduxのストアは、アプリを構成する状態、アクション、およびリデューサーをまとめます。ストアには、いくつかの責任があります

Reduxアプリケーションには、単一のストアのみがあることに注意することが重要です。データ処理ロジックを分割したい場合は、リデューサーの合成を使用して、個別のストアを作成するのではなく、組み合わせることができる複数のリデューサーを作成します。

ストアの作成

すべてのReduxストアには、単一のルートリデューサー関数があります。前のセクションでは、combineReducersを使用してルートリデューサー関数を作成しました。そのルートリデューサーは、現在、サンプルアプリのsrc/reducer.jsで定義されています。ルートリデューサーをインポートして、最初のストアを作成しましょう。

Reduxコアライブラリには、ストアを作成するcreateStore APIがあります。store.jsという新しいファイルを追加し、createStoreとルートリデューサーをインポートします。次に、createStoreを呼び出し、ルートリデューサーを渡します

src/store.js
import { createStore } from 'redux'
import rootReducer from './reducer'

const store = createStore(rootReducer)

export default store

初期状態の読み込み

createStoreは、2番目の引数としてpreloadedState値を受け入れることもできます。これを使用して、サーバーから送信されたHTMLページに含まれていた値や、localStorageに永続化され、ユーザーがページを再度訪れたときに読み戻された値など、ストアが作成されたときに初期データを追加できます。

storeStatePersistenceExample.js
import { createStore } from 'redux'
import rootReducer from './reducer'

let preloadedState
const persistedTodosString = localStorage.getItem('todos')

if (persistedTodosString) {
preloadedState = {
todos: JSON.parse(persistedTodosString)
}
}

const store = createStore(rootReducer, preloadedState)

アクションのディスパッチ

ストアを作成したので、プログラムが機能することを確認しましょう!UIがなくても、更新ロジックをテストできます。

ヒント

このコードを実行する前に、src/features/todos/todosSlice.jsに戻り、initialStateからすべてのToDoオブジェクトの例を削除して、空の配列になるようにしてください。これにより、この例の出力が少し読みやすくなります。

src/index.js
// Omit existing React imports

import store from './store'

// Log the initial state
console.log('Initial state: ', store.getState())
// {todos: [....], filters: {status, colors}}

// Every time the state changes, log it
// Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() =>
console.log('State after dispatch: ', store.getState())
)

// Now, dispatch some actions

store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about reducers' })
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about stores' })

store.dispatch({ type: 'todos/todoToggled', payload: 0 })
store.dispatch({ type: 'todos/todoToggled', payload: 1 })

store.dispatch({ type: 'filters/statusFilterChanged', payload: 'Active' })

store.dispatch({
type: 'filters/colorFilterChanged',
payload: { color: 'red', changeType: 'added' }
})

// Stop listening to state updates
unsubscribe()

// Dispatch one more action to see what happens

store.dispatch({ type: 'todos/todoAdded', payload: 'Try creating a store' })

// Omit existing React rendering logic

store.dispatch(action)を呼び出すたびに、次のようになります

  • ストアはrootReducer(state, action)を呼び出します
    • そのルートリデューサーは、todosReducer(state.todos, action)のように、内部で他のスライスリデューサーを呼び出す場合があります
  • ストアは内部に新しい状態値を保存します
  • ストアはすべてのリスナーサブスクリプションコールバックを呼び出します
  • リスナーがstoreにアクセスできる場合、store.getState()を呼び出して最新の状態値を読み取ることができます

その例からのコンソールログの出力を確認すると、各アクションがディスパッチされるにつれて、Reduxの状態がどのように変化するかがわかります

Logged Redux state after dispatching actions

最後の操作からアプリが何もログに記録しなかったことに注意してください。これは、unsubscribe()を呼び出したときにリスナーコールバックを削除したため、アクションがディスパッチされた後に何も実行されなかったためです。

UIを書き始める前に、アプリの動作を指定しました。これにより、アプリが意図したとおりに動作するという確信を得ることができます。

情報

必要であれば、リデューサーのテストを試すことができます。それらは純粋関数であるため、簡単にテストできます。例のstateactionを使用して呼び出し、結果を取得して、期待どおりの結果と一致するかどうかを確認してください

todosSlice.spec.js
import todosReducer from './todosSlice'

test('Toggles a todo based on id', () => {
const initialState = [{ id: 0, text: 'Test text', completed: false }]

const action = { type: 'todos/todoToggled', payload: 0 }
const result = todosReducer(initialState, action)
expect(result[0].completed).toBe(true)
})

Reduxストアの内部

Reduxストアの中を覗いて、どのように動作するかを確認すると役立つ場合があります。以下は、約25行のコードで記述された、動作中のReduxストアのミニチュア例です

miniReduxStoreExample.js
function createStore(reducer, preloadedState) {
let state = preloadedState
const listeners = []

function getState() {
return state
}

function subscribe(listener) {
listeners.push(listener)
return function unsubscribe() {
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}

function dispatch(action) {
state = reducer(state, action)
listeners.forEach(listener => listener())
}

dispatch({ type: '@@redux/INIT' })

return { dispatch, subscribe, getState }
}

このReduxストアの小さなバージョンは十分に機能するため、これまでアプリで使用していた実際のReduxのcreateStore関数を置き換えることができます。(試して自分で確認してください!)実際のReduxストアの実装はより長く、少し複雑ですが、そのほとんどはコメント、警告メッセージ、およびいくつかのエッジケースの処理です。

ご覧のとおり、ここでの実際のロジックは非常に短いです

  • ストアには、現在のstate値とreducer関数が内部にあります
  • getStateは現在の状態値を返します
  • subscribeはリスナーコールバックの配列を保持し、新しいコールバックを削除する関数を返します
  • dispatchはリデューサーを呼び出し、状態を保存し、リスナーを実行します
  • ストアは、起動時に1つのアクションをディスパッチして、状態を持つリデューサーを初期化します
  • ストアAPIは、{dispatch, subscribe, getState}が内部にあるオブジェクトです

特にその1つを強調するために、getStateは現在のstate値を返すだけであることに注意してください。これは、デフォルトでは、現在の状態値を誤ってミューテーションすることを防ぐものは何もないことを意味します!このコードはエラーなしで実行されますが、正しくありません

const state = store.getState()
// ❌ Don't do this - it mutates the current state!
state.filters.status = 'Active'

つまり

  • Reduxストアは、getState()を呼び出したときにstate値の追加コピーを作成しません。これは、ルートリデューサー関数から返されたものとまったく同じ参照です
  • Reduxストアは、偶発的なミューテーションを防ぐために他に何も行いません。リデューサー内またはストアの外で状態をミューテーションすることは可能であり、常にミューテーションを避けるように注意する必要があります。

偶発的なミューテーションの一般的な原因の1つは、配列のソートです。array.sort()を呼び出すと、実際には既存の配列がミューテーションされますconst sortedTodos = state.todos.sort()を呼び出した場合、意図せずに実際のストアの状態をミューテーションすることになります。

ヒント

パート8:モダンReduxでは、Redux Toolkitがどのようにリデューサーでのミューテーションを回避し、リデューサー外での偶発的なミューテーションを検出し警告するかを見ていきます。

ストアの設定

これまで、rootReducerpreloadedStateの引数をcreateStoreに渡せることを見てきました。しかし、createStoreはストアの機能をカスタマイズし、新しい能力を与えるために使用できる、もう1つの引数を受け取ることができます。

Reduxストアは、ストアエンハンサーと呼ばれるものを使用してカスタマイズされます。ストアエンハンサーは、元のReduxストアをラップする別のレイヤーを追加する、特別なバージョンのcreateStoreのようなものです。エンハンスされたストアは、元のストアのdispatchgetState、およびsubscribe関数の代わりに独自のバージョンを提供することで、ストアの動作を変更できます。

このチュートリアルでは、ストアエンハンサーが実際にどのように機能するかについては詳しく説明しません。それらの使用方法に焦点を当てます。

エンハンサーによるストアの作成

私たちのプロジェクトには、src/exampleAddons/enhancers.jsファイルに、2つの小さなストアエンハンサーの例が用意されています。

  • sayHiOnDispatch:アクションがディスパッチされるたびに、コンソールに'Hi'!を常にログ出力するエンハンサー
  • includeMeaningOfLifegetState()から返される値に、常にフィールドmeaningOfLife: 42を追加するエンハンサー

まず、sayHiOnDispatchを使用してみましょう。最初に、インポートして、createStoreに渡します。

src/store.js
import { createStore } from 'redux'
import rootReducer from './reducer'
import { sayHiOnDispatch } from './exampleAddons/enhancers'

const store = createStore(rootReducer, undefined, sayHiOnDispatch)

export default store

ここにはpreloadedStateの値がないため、代わりに2番目の引数としてundefinedを渡します。

次に、アクションをディスパッチしてみましょう。

src/index.js
import store from './store'

console.log('Dispatching action')
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
console.log('Dispatch complete')

コンソールを見てください。他の2つのログステートメントの間に、'Hi!'がログに記録されているはずです。

sayHi store enhancer logging

sayHiOnDispatchエンハンサーは、元のstore.dispatch関数を、独自の特殊なバージョンのdispatchでラップしました。store.dispatch()を呼び出したとき、実際にはsayHiOnDispatchのラッパー関数を呼び出しており、それが元の関数を呼び出し、その後「Hi」を出力しました。

次に、2つ目のエンハンサーを追加してみましょう。同じファイルからincludeMeaningOfLifeをインポートできますが、問題があります。createStoreは、3番目の引数として1つのエンハンサーのみを受け入れます!どうすれば、2つのエンハンサーを同時に渡せるでしょうか?

本当に必要なのは、sayHiOnDispatchエンハンサーとincludeMeaningOfLifeエンハンサーの両方を、1つの結合されたエンハンサーにマージし、それを代わりに渡す方法です。

幸いなことに、Reduxコアには、複数のエンハンサーをマージするために使用できるcompose関数が含まれています。ここでそれを使用してみましょう。

src/store.js
import { createStore, compose } from 'redux'
import rootReducer from './reducer'
import {
sayHiOnDispatch,
includeMeaningOfLife
} from './exampleAddons/enhancers'

const composedEnhancer = compose(sayHiOnDispatch, includeMeaningOfLife)

const store = createStore(rootReducer, undefined, composedEnhancer)

export default store

これで、ストアを使用した場合に何が起こるかを確認できます。

src/index.js
import store from './store'

store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
// log: 'Hi!'

console.log('State after dispatch: ', store.getState())
// log: {todos: [...], filters: {status, colors}, meaningOfLife: 42}

そして、ログ出力は次のようになります。

meaningOfLife store enhancer logging

したがって、両方のエンハンサーが同時にストアの動作を変更していることがわかります。sayHiOnDispatchdispatchの動作方法を変更し、includeMeaningOfLifegetStateの動作方法を変更しました。

ストアエンハンサーは、ストアを変更するための非常に強力な方法であり、ほとんどすべてのReduxアプリは、ストアを設定する際に少なくとも1つのエンハンサーを含みます。

ヒント

渡すpreloadedStateがない場合は、代わりに2番目の引数としてenhancerを渡すことができます。

const store = createStore(rootReducer, storeEnhancer)

ミドルウェア

エンハンサーは、ストアのメソッド(dispatchgetState、およびsubscribe)のいずれかをオーバーライドまたは置き換えることができるため、強力です。

しかし、ほとんどの場合、dispatchの動作をカスタマイズする必要があるだけです。dispatchが実行されるときに、何らかのカスタマイズされた動作を追加する方法があると便利です。

Reduxは、ミドルウェアと呼ばれる特別な種類のアドオンを使用して、dispatch関数をカスタマイズできるようにします。

ExpressやKoaのようなライブラリを使用したことがある場合、動作をカスタマイズするためにミドルウェアを追加するという考えに既におなじみかもしれません。これらのフレームワークでは、ミドルウェアは、フレームワークがリクエストを受信してから、フレームワークがレスポンスを生成するまでの間に配置できるコードです。たとえば、ExpressまたはKoaミドルウェアは、CORSヘッダー、ロギング、圧縮などを追加する場合があります。ミドルウェアの最大の機能は、チェーンで構成できることです。単一のプロジェクトで複数の独立したサードパーティ製ミドルウェアを使用できます。

Reduxミドルウェアは、ExpressまたはKoaミドルウェアとは異なる問題を解決しますが、概念的には同様の方法で解決します。Reduxミドルウェアは、アクションのディスパッチと、それがリデューサーに到達する瞬間の間に、サードパーティの拡張ポイントを提供します。人々は、ロギング、クラッシュレポート、非同期APIとの通信、ルーティングなどにReduxミドルウェアを使用します。

まず、ストアにミドルウェアを追加する方法を見て、次に独自のミドルウェアを作成する方法を示します。

ミドルウェアの使用

ストアエンハンサーを使用してReduxストアをカスタマイズできることを既に説明しました。Reduxミドルウェアは、実際には、Reduxに組み込まれている非常に特別なストアエンハンサー(applyMiddleware)の上に実装されています。

ストアにエンハンサーを追加する方法は既にわかっているので、今すぐ追加できるはずです。まず、applyMiddleware単独で使用し、このプロジェクトに含まれている3つのミドルウェアの例を追加します。

src/store.js
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './reducer'
import { print1, print2, print3 } from './exampleAddons/middleware'

const middlewareEnhancer = applyMiddleware(print1, print2, print3)

// Pass enhancer as the second arg, since there's no preloadedState
const store = createStore(rootReducer, middlewareEnhancer)

export default store

名前が示すように、これらのミドルウェアはそれぞれ、アクションがディスパッチされるときに番号を出力します。

今、ディスパッチするとどうなるでしょうか?

src/index.js
import store from './store'

store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
// log: '1'
// log: '2'
// log: '3'

コンソールに出力を確認できます。

print middleware logging

どのように機能するのでしょうか?

ミドルウェアは、ストアのdispatchメソッドの周りにパイプラインを形成しますstore.dispatch(action)を呼び出すと、実際にはパイプラインの最初のミドルウェアを呼び出しています。そのミドルウェアは、アクションを見たときに何でも実行できます。通常、ミドルウェアは、リデューサーと同じように、アクションが特定のタイプであるかどうかを確認します。正しいタイプの場合、ミドルウェアはいくつかのカスタムロジックを実行する可能性があります。それ以外の場合は、アクションをパイプライン内の次のミドルウェアに渡します。

リデューサーとは異なりミドルウェアはタイムアウトやその他の非同期ロジックを含む副作用を持つことができます

この場合、アクションは通過します。

  1. print1ミドルウェア(store.dispatchとして表示される)
  2. print2ミドルウェア
  3. print3ミドルウェア
  4. 元のstore.dispatch
  5. store内のルートリデューサー

そして、これらはすべて関数呼び出しであるため、すべてそのコールスタックから戻ります。したがって、print1ミドルウェアは最初に実行され、最後に終了します。

カスタムミドルウェアの作成

独自のミドルウェアを作成することもできます。これを常に実行する必要はないかもしれませんが、カスタムミドルウェアは、Reduxアプリケーションに特定の動作を追加するための優れた方法です。

Reduxミドルウェアは、3つのネストされた関数の連なりとして記述されます。そのパターンがどのように見えるかを見てみましょう。最初に、functionキーワードを使用してこのミドルウェアを記述してみます。そうすることで、何が起こっているかがより明確になります。

// Middleware written as ES5 functions

// Outer function:
function exampleMiddleware(storeAPI) {
return function wrapDispatch(next) {
return function handleAction(action) {
// Do anything here: pass the action onwards with next(action),
// or restart the pipeline with storeAPI.dispatch(action)
// Can also use storeAPI.getState() here

return next(action)
}
}
}

これらの3つの関数が何を行い、その引数が何であるかを分解してみましょう。

  • exampleMiddleware:外側の関数は、実際には「ミドルウェア」自体です。applyMiddlewareによって呼び出され、ストアの{dispatch, getState}関数を含むstoreAPIオブジェクトを受け取ります。これらは、実際にストアの一部であるものと同じdispatchおよびgetState関数です。このdispatch関数を呼び出すと、アクションがミドルウェアパイプラインの先頭に送信されます。これは1回だけ呼び出されます。
  • wrapDispatch:中間の関数は、引数としてnextという関数を受け取ります。この関数は、実際にはパイプライン内の次のミドルウェアです。このミドルウェアがシーケンスの最後のミドルウェアである場合、nextは実際には元のstore.dispatch関数になります。next(action)を呼び出すと、パイプライン内の次のミドルウェアにアクションが渡されます。これも1回だけ呼び出されます。
  • handleAction:最後に、内側の関数は、現在のactionを引数として受け取り、アクションがディスパッチされるたびに呼び出されます。
ヒント

これらのミドルウェア関数には任意の名前を付けることができますが、それぞれの関数が何をするかを覚えておくために、これらの名前を使用すると役立ちます。

  • 外側:someCustomMiddleware(またはミドルウェアの名前)
  • 中間:wrapDispatch
  • 内側:handleAction

これらは通常の関数であるため、ES2015アロー関数を使用して記述することもできます。これにより、アロー関数はreturnステートメントを持つ必要がないため、記述が短くなりますが、アロー関数と暗黙の戻り値にまだ慣れていない場合は、少し読みにくくなる可能性もあります。

以下は、アロー関数を使用した上記と同じ例です。

const anotherExampleMiddleware = storeAPI => next => action => {
// Do something in here, when each action is dispatched

return next(action)
}

これら3つの関数をネストして、各関数を返していますが、暗黙の戻り値により、記述が短くなります。

最初のカスタムミドルウェア

アプリケーションにロギングを追加したいとしましょう。アクションがディスパッチされたときに、各アクションの内容をコンソールで確認し、アクションがリデューサーによって処理された後、状態がどうなっているかを確認したいと考えています。

情報

これらのミドルウェアの例は、実際のtodoアプリの特定の部分ではありませんが、プロジェクトに追加して、使用したときに何が起こるかを確認できます。

その情報をコンソールにログ出力する小さなミドルウェアを記述できます。

const loggerMiddleware = storeAPI => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', storeAPI.getState())
return result
}

アクションがディスパッチされるたびに

  • handleAction関数の最初の部分が実行され、'dispatching'を出力します。
  • アクションをnextセクションに渡します。これは、別のミドルウェアまたは実際のstore.dispatchである可能性があります。
  • 最終的にリデューサーが実行され、状態が更新され、next関数が戻ります。
  • これで、storeAPI.getState()を呼び出して、新しい状態を確認できます。
  • nextミドルウェアから返されたresult値を返して終了します。

任意のミドルウェアが任意の値を返すことができ、パイプライン内の最初のミドルウェアからの戻り値は、store.dispatch()を呼び出すときに実際に戻されます。例:

const alwaysReturnHelloMiddleware = storeAPI => next => action => {
const originalResult = next(action)
// Ignore the original result, return something else
return 'Hello!'
}

const middlewareEnhancer = applyMiddleware(alwaysReturnHelloMiddleware)
const store = createStore(rootReducer, middlewareEnhancer)

const dispatchResult = store.dispatch({ type: 'some/action' })
console.log(dispatchResult)
// log: 'Hello!'

もう1つの例を試してみましょう。ミドルウェアは、特定のイベントを探し、そのイベントがディスパッチされたときに何かを行うことがよくあります。ミドルウェアには、内部で非同期ロジックを実行する機能もあります。特定のアクションが表示されたときに、遅延して何かを出力するミドルウェアを記述できます。

const delayedMessageMiddleware = storeAPI => next => action => {
if (action.type === 'todos/todoAdded') {
setTimeout(() => {
console.log('Added a new todo: ', action.payload)
}, 1000)
}

return next(action)
}

このミドルウェアは、「todo added」アクションを探します。それが見つかるたびに、1秒のタイマーを設定し、アクションのペイロードをコンソールに出力します。

ミドルウェアの使用例

では、ミドルウェアで何ができるのでしょうか?たくさんのことがあります!

ミドルウェアは、ディスパッチされたアクションを見たときに、何でも実行できます。

  • コンソールに何かをログ出力する
  • タイムアウトを設定する
  • 非同期API呼び出しを行う
  • アクションを変更する
  • アクションを一時停止するか、完全に停止する

その他、思いつくことは何でも

特に、ミドルウェアは、副作用のあるロジックを含めることを意図しています。さらに、ミドルウェアは、プレーンアクションオブジェクトではないものを受け入れるようにdispatchを変更できます。この2つについては、パート6:非同期ロジックで詳しく説明します。

Redux DevTools

最後に、ストアの設定で説明するべき、もう1つの非常に重要なことがあります。

Reduxは、状態がいつ、どこで、なぜ、どのように時間とともに変化したかをより簡単に理解できるように特別に設計されました。その一環として、Reduxは、Redux DevToolsの使用を可能にするように構築されました。これは、ディスパッチされたアクションの履歴、それらのアクションに含まれていた内容、および各ディスパッチされたアクションの後に状態がどのように変化したかを示すアドオンです。

Redux DevTools UI は、ChromeFirefox のブラウザ拡張機能として利用できます。まだブラウザに追加していない場合は、今すぐ追加してください。

インストールが完了したら、ブラウザの開発者ツールウィンドウを開いてください。そこに新しい「Redux」タブが表示されるはずです。まだ何も機能しません。まず Redux ストアと通信できるように設定する必要があります。

ストアへの DevTools の追加

拡張機能がインストールされたら、DevTools が内部で何が起こっているかを確認できるようにストアを設定する必要があります。DevTools を有効にするには、特別なストアエンハンサーを追加する必要があります。

Redux DevTools Extension のドキュメントには、ストアの設定方法に関する説明がありますが、記載されている手順は少し複雑です。しかし、redux-devtools-extension という NPM パッケージを使用すれば、複雑な部分を処理できます。このパッケージは、オリジナルの Redux の compose 関数の代わりに使える、特殊な composeWithDevTools 関数をエクスポートします。

これがどのように見えるかです。

src/store.js
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducer'
import { print1, print2, print3 } from './exampleAddons/middleware'

const composedEnhancer = composeWithDevTools(
// EXAMPLE: Add whatever middleware you actually want to use here
applyMiddleware(print1, print2, print3)
// other store enhancers if any
)

const store = createStore(rootReducer, composedEnhancer)
export default store

index.js がストアをインポートした後もアクションをディスパッチしていることを確認してください。次に、ブラウザの開発者ツールウィンドウの Redux DevTools タブを開きます。次のようなものが表示されるはずです。

Redux DevTools Extension: action tab

左側にディスパッチされたアクションの一覧が表示されています。いずれかをクリックすると、右側のペインにいくつかのタブが表示されます。

  • そのアクションオブジェクトの内容
  • リデューサーが実行された後の Redux の状態全体
  • 前の状態とこの状態の差分
  • 有効になっている場合、store.dispatch() を最初に呼び出したコード行に至る関数スタックトレース

「todo を追加」アクションをディスパッチした後、「State」タブと「Diff」タブがどのように表示されるかを以下に示します。

Redux DevTools Extension: state tab

Redux DevTools Extension: diff tab

これらは、アプリをデバッグし、内部で何が起こっているのかを正確に理解するのに役立つ非常に強力なツールです。

学んだこと

見てきたように、ストアはすべての Redux アプリケーションの中心的な要素です。ストアは状態を含み、リデューサーを実行してアクションを処理し、追加の動作を追加するためにカスタマイズできます。

例のアプリが現在どのように見えるかを見てみましょう。

そして、念のため、このセクションで取り上げた内容を以下に示します。

まとめ
  • Redux アプリには常に単一のストアがあります。
    • ストアは Redux の createStore API で作成されます。
    • すべてのストアには、単一のルートリデューサー関数があります。
  • ストアには 3 つの主要なメソッドがあります。
    • getState は現在の状態を返します。
    • dispatch は、状態を更新するためにリデューサーにアクションを送信します。
    • subscribe は、アクションがディスパッチされるたびに実行されるリスナーコールバックを受け取ります。
  • ストアエンハンサーを使用すると、ストアの作成時にカスタマイズできます。
    • エンハンサーはストアをラップし、そのメソッドをオーバーライドできます。
    • createStore は引数として 1 つのエンハンサーを受け取ります。
    • 複数のエンハンサーは、compose API を使用してマージできます。
  • ミドルウェアは、ストアをカスタマイズする主な方法です。
    • ミドルウェアは、applyMiddleware エンハンサーを使用して追加されます。
    • ミドルウェアは、互いにネストされた 3 つの関数として記述されます。
    • ミドルウェアは、アクションがディスパッチされるたびに実行されます。
    • ミドルウェアには内部に副作用を含めることができます。
  • Redux DevTools を使用すると、アプリで時間の経過とともに変化した内容を確認できます。
    • DevTools 拡張機能はブラウザにインストールできます。
    • ストアには、composeWithDevTools を使用して追加された DevTools エンハンサーが必要です。
    • DevTools は、ディスパッチされたアクションと、時間の経過に伴う状態の変化を表示します。

次は何?

これで、リデューサーを実行し、アクションをディスパッチしたときに状態を更新できる、動作する Redux ストアができました。

しかし、すべてのアプリには、データを表示し、ユーザーが何か便利なことができるようにするユーザーインターフェイスが必要です。パート 5: UI と React では、Redux ストアが UI とどのように連携するか、特に Redux が React とどのように連携できるかを見ていきます。