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 Essentials」チュートリアル。これは、実際のアプリ向けのRedux Toolkitを使用した「Reduxの正しい使い方」を教えます。すべてのRedux学習者は、「Essentials」チュートリアルを読むことをお勧めします!
- Reduxの基礎、パート8: Redux Toolkitを使用したモダンRedux。これは、以前のセクションの低レベルの例を、現代のRedux Toolkitの同等物に変換する方法を示します
Reduxストア
Reduxのストアは、アプリを構成する状態、アクション、およびリデューサーをまとめます。ストアには、いくつかの責任があります
- 現在のアプリケーションの状態を内部に保持します
store.getState()
経由で現在の状態へのアクセスを許可します。store.dispatch(action)
経由で状態を更新できるようにします。store.subscribe(listener)
経由でリスナーコールバックを登録します。store.subscribe(listener)
によって返されるunsubscribe
関数を介してリスナーの登録解除を処理します。
Reduxアプリケーションには、単一のストアのみがあることに注意することが重要です。データ処理ロジックを分割したい場合は、リデューサーの合成を使用して、個別のストアを作成するのではなく、組み合わせることができる複数のリデューサーを作成します。
ストアの作成
すべてのReduxストアには、単一のルートリデューサー関数があります。前のセクションでは、combineReducers
を使用してルートリデューサー関数を作成しました。そのルートリデューサーは、現在、サンプルアプリのsrc/reducer.js
で定義されています。ルートリデューサーをインポートして、最初のストアを作成しましょう。
Reduxコアライブラリには、ストアを作成するcreateStore
APIがあります。store.js
という新しいファイルを追加し、createStore
とルートリデューサーをインポートします。次に、createStore
を呼び出し、ルートリデューサーを渡します
import { createStore } from 'redux'
import rootReducer from './reducer'
const store = createStore(rootReducer)
export default store
初期状態の読み込み
createStore
は、2番目の引数としてpreloadedState
値を受け入れることもできます。これを使用して、サーバーから送信されたHTMLページに含まれていた値や、localStorage
に永続化され、ユーザーがページを再度訪れたときに読み戻された値など、ストアが作成されたときに初期データを追加できます。
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オブジェクトの例を削除して、空の配列になるようにしてください。これにより、この例の出力が少し読みやすくなります。
// 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の状態がどのように変化するかがわかります
最後の操作からアプリが何もログに記録しなかったことに注意してください。これは、unsubscribe()
を呼び出したときにリスナーコールバックを削除したため、アクションがディスパッチされた後に何も実行されなかったためです。
UIを書き始める前に、アプリの動作を指定しました。これにより、アプリが意図したとおりに動作するという確信を得ることができます。
必要であれば、リデューサーのテストを試すことができます。それらは純粋関数であるため、簡単にテストできます。例のstate
とaction
を使用して呼び出し、結果を取得して、期待どおりの結果と一致するかどうかを確認してください
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ストアのミニチュア例です
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がどのようにリデューサーでのミューテーションを回避し、リデューサー外での偶発的なミューテーションを検出し警告するかを見ていきます。
ストアの設定
これまで、rootReducer
とpreloadedState
の引数をcreateStore
に渡せることを見てきました。しかし、createStore
はストアの機能をカスタマイズし、新しい能力を与えるために使用できる、もう1つの引数を受け取ることができます。
Reduxストアは、ストアエンハンサーと呼ばれるものを使用してカスタマイズされます。ストアエンハンサーは、元のReduxストアをラップする別のレイヤーを追加する、特別なバージョンのcreateStore
のようなものです。エンハンスされたストアは、元のストアのdispatch
、getState
、およびsubscribe
関数の代わりに独自のバージョンを提供することで、ストアの動作を変更できます。
このチュートリアルでは、ストアエンハンサーが実際にどのように機能するかについては詳しく説明しません。それらの使用方法に焦点を当てます。
エンハンサーによるストアの作成
私たちのプロジェクトには、src/exampleAddons/enhancers.js
ファイルに、2つの小さなストアエンハンサーの例が用意されています。
sayHiOnDispatch
:アクションがディスパッチされるたびに、コンソールに'Hi'!
を常にログ出力するエンハンサーincludeMeaningOfLife
:getState()
から返される値に、常にフィールドmeaningOfLife: 42
を追加するエンハンサー
まず、sayHiOnDispatch
を使用してみましょう。最初に、インポートして、createStore
に渡します。
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
を渡します。
次に、アクションをディスパッチしてみましょう。
import store from './store'
console.log('Dispatching action')
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
console.log('Dispatch complete')
コンソールを見てください。他の2つのログステートメントの間に、'Hi!'
がログに記録されているはずです。
sayHiOnDispatch
エンハンサーは、元のstore.dispatch
関数を、独自の特殊なバージョンのdispatch
でラップしました。store.dispatch()
を呼び出したとき、実際にはsayHiOnDispatch
のラッパー関数を呼び出しており、それが元の関数を呼び出し、その後「Hi」を出力しました。
次に、2つ目のエンハンサーを追加してみましょう。同じファイルからincludeMeaningOfLife
をインポートできますが、問題があります。createStore
は、3番目の引数として1つのエンハンサーのみを受け入れます!どうすれば、2つのエンハンサーを同時に渡せるでしょうか?
本当に必要なのは、sayHiOnDispatch
エンハンサーとincludeMeaningOfLife
エンハンサーの両方を、1つの結合されたエンハンサーにマージし、それを代わりに渡す方法です。
幸いなことに、Reduxコアには、複数のエンハンサーをマージするために使用できるcompose
関数が含まれています。ここでそれを使用してみましょう。
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
これで、ストアを使用した場合に何が起こるかを確認できます。
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}
そして、ログ出力は次のようになります。
したがって、両方のエンハンサーが同時にストアの動作を変更していることがわかります。sayHiOnDispatch
はdispatch
の動作方法を変更し、includeMeaningOfLife
はgetState
の動作方法を変更しました。
ストアエンハンサーは、ストアを変更するための非常に強力な方法であり、ほとんどすべてのReduxアプリは、ストアを設定する際に少なくとも1つのエンハンサーを含みます。
渡すpreloadedState
がない場合は、代わりに2番目の引数としてenhancer
を渡すことができます。
const store = createStore(rootReducer, storeEnhancer)
ミドルウェア
エンハンサーは、ストアのメソッド(dispatch
、getState
、およびsubscribe
)のいずれかをオーバーライドまたは置き換えることができるため、強力です。
しかし、ほとんどの場合、dispatch
の動作をカスタマイズする必要があるだけです。dispatch
が実行されるときに、何らかのカスタマイズされた動作を追加する方法があると便利です。
Reduxは、ミドルウェアと呼ばれる特別な種類のアドオンを使用して、dispatch
関数をカスタマイズできるようにします。
ExpressやKoaのようなライブラリを使用したことがある場合、動作をカスタマイズするためにミドルウェアを追加するという考えに既におなじみかもしれません。これらのフレームワークでは、ミドルウェアは、フレームワークがリクエストを受信してから、フレームワークがレスポンスを生成するまでの間に配置できるコードです。たとえば、ExpressまたはKoaミドルウェアは、CORSヘッダー、ロギング、圧縮などを追加する場合があります。ミドルウェアの最大の機能は、チェーンで構成できることです。単一のプロジェクトで複数の独立したサードパーティ製ミドルウェアを使用できます。
Reduxミドルウェアは、ExpressまたはKoaミドルウェアとは異なる問題を解決しますが、概念的には同様の方法で解決します。Reduxミドルウェアは、アクションのディスパッチと、それがリデューサーに到達する瞬間の間に、サードパーティの拡張ポイントを提供します。人々は、ロギング、クラッシュレポート、非同期APIとの通信、ルーティングなどにReduxミドルウェアを使用します。
まず、ストアにミドルウェアを追加する方法を見て、次に独自のミドルウェアを作成する方法を示します。
ミドルウェアの使用
ストアエンハンサーを使用してReduxストアをカスタマイズできることを既に説明しました。Reduxミドルウェアは、実際には、Reduxに組み込まれている非常に特別なストアエンハンサー(applyMiddleware
)の上に実装されています。
ストアにエンハンサーを追加する方法は既にわかっているので、今すぐ追加できるはずです。まず、applyMiddleware
単独で使用し、このプロジェクトに含まれている3つのミドルウェアの例を追加します。
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
名前が示すように、これらのミドルウェアはそれぞれ、アクションがディスパッチされるときに番号を出力します。
今、ディスパッチするとどうなるでしょうか?
import store from './store'
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
// log: '1'
// log: '2'
// log: '3'
コンソールに出力を確認できます。
どのように機能するのでしょうか?
ミドルウェアは、ストアのdispatch
メソッドの周りにパイプラインを形成します。store.dispatch(action)
を呼び出すと、実際にはパイプラインの最初のミドルウェアを呼び出しています。そのミドルウェアは、アクションを見たときに何でも実行できます。通常、ミドルウェアは、リデューサーと同じように、アクションが特定のタイプであるかどうかを確認します。正しいタイプの場合、ミドルウェアはいくつかのカスタムロジックを実行する可能性があります。それ以外の場合は、アクションをパイプライン内の次のミドルウェアに渡します。
リデューサーとは異なり、ミドルウェアはタイムアウトやその他の非同期ロジックを含む副作用を持つことができます。
この場合、アクションは通過します。
print1
ミドルウェア(store.dispatch
として表示される)print2
ミドルウェアprint3
ミドルウェア- 元の
store.dispatch
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 は、Chrome と Firefox のブラウザ拡張機能として利用できます。まだブラウザに追加していない場合は、今すぐ追加してください。
インストールが完了したら、ブラウザの開発者ツールウィンドウを開いてください。そこに新しい「Redux」タブが表示されるはずです。まだ何も機能しません。まず Redux ストアと通信できるように設定する必要があります。
ストアへの DevTools の追加
拡張機能がインストールされたら、DevTools が内部で何が起こっているかを確認できるようにストアを設定する必要があります。DevTools を有効にするには、特別なストアエンハンサーを追加する必要があります。
Redux DevTools Extension のドキュメントには、ストアの設定方法に関する説明がありますが、記載されている手順は少し複雑です。しかし、redux-devtools-extension
という NPM パッケージを使用すれば、複雑な部分を処理できます。このパッケージは、オリジナルの Redux の compose
関数の代わりに使える、特殊な composeWithDevTools
関数をエクスポートします。
これがどのように見えるかです。
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 の状態全体
- 前の状態とこの状態の差分
- 有効になっている場合、
store.dispatch()
を最初に呼び出したコード行に至る関数スタックトレース
「todo を追加」アクションをディスパッチした後、「State」タブと「Diff」タブがどのように表示されるかを以下に示します。
これらは、アプリをデバッグし、内部で何が起こっているのかを正確に理解するのに役立つ非常に強力なツールです。
学んだこと
見てきたように、ストアはすべての Redux アプリケーションの中心的な要素です。ストアは状態を含み、リデューサーを実行してアクションを処理し、追加の動作を追加するためにカスタマイズできます。
例のアプリが現在どのように見えるかを見てみましょう。
そして、念のため、このセクションで取り上げた内容を以下に示します。
- Redux アプリには常に単一のストアがあります。
- ストアは Redux の
createStore
API で作成されます。 - すべてのストアには、単一のルートリデューサー関数があります。
- ストアは Redux の
- ストアには 3 つの主要なメソッドがあります。
getState
は現在の状態を返します。dispatch
は、状態を更新するためにリデューサーにアクションを送信します。subscribe
は、アクションがディスパッチされるたびに実行されるリスナーコールバックを受け取ります。
- ストアエンハンサーを使用すると、ストアの作成時にカスタマイズできます。
- エンハンサーはストアをラップし、そのメソッドをオーバーライドできます。
createStore
は引数として 1 つのエンハンサーを受け取ります。- 複数のエンハンサーは、
compose
API を使用してマージできます。
- ミドルウェアは、ストアをカスタマイズする主な方法です。
- ミドルウェアは、
applyMiddleware
エンハンサーを使用して追加されます。 - ミドルウェアは、互いにネストされた 3 つの関数として記述されます。
- ミドルウェアは、アクションがディスパッチされるたびに実行されます。
- ミドルウェアには内部に副作用を含めることができます。
- ミドルウェアは、
- Redux DevTools を使用すると、アプリで時間の経過とともに変化した内容を確認できます。
- DevTools 拡張機能はブラウザにインストールできます。
- ストアには、
composeWithDevTools
を使用して追加された DevTools エンハンサーが必要です。 - DevTools は、ディスパッチされたアクションと、時間の経過に伴う状態の変化を表示します。
次は何?
これで、リデューサーを実行し、アクションをディスパッチしたときに状態を更新できる、動作する Redux ストアができました。
しかし、すべてのアプリには、データを表示し、ユーザーが何か便利なことができるようにするユーザーインターフェイスが必要です。パート 5: UI と React では、Redux ストアが UI とどのように連携するか、特に Redux が React とどのように連携できるかを見ていきます。