ストアの設定
「Redux の基本」チュートリアルでは、Todo リストアプリの例を作成することで、Redux の基本的な概念を紹介しました。その中で、Redux ストアの作成と設定方法について説明しました。
ここでは、追加の機能を追加するためにストアをカスタマイズする方法について説明します。まず、「Redux の基本」パート 5:UI と Reactのソースコードから始めます。このチュートリアルのこの段階のソースは、Github のサンプルアプリのリポジトリ、またはCodeSandbox を介してブラウザで確認できます。
ストアの作成
まず、ストアを作成した元の index.js
ファイルを見てみましょう。
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
import App from './components/App'
const store = createStore(rootReducer)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
このコードでは、リデューサーを Redux の createStore
関数に渡し、store
オブジェクトを返します。次に、このオブジェクトを react-redux
の Provider
コンポーネントに渡し、コンポーネントツリーの最上位にレンダリングします。
これにより、react-redux
の connect
を介してアプリで Redux に接続するときはいつでも、ストアがコンポーネントで利用できるようになります。
Redux 機能の拡張
ほとんどのアプリでは、ミドルウェアまたはストアエンハンサーを追加することで、Redux ストアの機能を拡張します(注: ミドルウェアは一般的ですが、エンハンサーはあまり一般的ではありません)。ミドルウェアは Redux の dispatch
関数に追加の機能を追加します。エンハンサーは Redux ストアに追加の機能を追加します。
2 つのミドルウェアと 1 つのエンハンサーを追加します。
- ディスパッチの簡単な非同期使用を可能にする、
redux-thunk
ミドルウェア。 - ディスパッチされたアクションと結果の新しい状態をログに記録するミドルウェア。
- 各アクションを処理するためにリデューサーが費やした時間をログに記録するエンハンサー。
redux-thunk
のインストール
npm install redux-thunk
middleware/logger.js
const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}
export default logger
enhancers/monitorReducer.js
const round = number => Math.round(number * 100) / 100
const monitorReducerEnhancer =
createStore => (reducer, initialState, enhancer) => {
const monitoredReducer = (state, action) => {
const start = performance.now()
const newState = reducer(state, action)
const end = performance.now()
const diff = round(end - start)
console.log('reducer process time:', diff)
return newState
}
return createStore(monitoredReducer, initialState, enhancer)
}
export default monitorReducerEnhancer
これらを既存の index.js
に追加しましょう。
まず、
redux-thunk
と、loggerMiddleware
およびmonitorReducerEnhancer
、および Redux が提供する 2 つの追加関数 (applyMiddleware
とcompose
) をインポートする必要があります。次に、
applyMiddleware
を使用して、loggerMiddleware
とthunkMiddleware
をストアのディスパッチ関数に適用するストアエンハンサーを作成します。次に、
compose
を使用して、新しいmiddlewareEnhancer
とmonitorReducerEnhancer
を 1 つの関数に合成します。これは、
createStore
に渡すことができるエンハンサーが 1 つだけであるため必要です。複数のエンハンサーを使用するには、まずこの例に示すように、エンハンサーを 1 つの大きなエンハンサーに合成する必要があります。最後に、この新しい
composedEnhancers
関数をcreateStore
の 3 番目の引数として渡します。注: 2 番目の引数は、無視しますが、ストアにプリロードされた状態を設定できます。
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { applyMiddleware, createStore, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from './reducers'
import loggerMiddleware from './middleware/logger'
import monitorReducerEnhancer from './enhancers/monitorReducer'
import App from './components/App'
const middlewareEnhancer = applyMiddleware(loggerMiddleware, thunkMiddleware)
const composedEnhancers = compose(middlewareEnhancer, monitorReducerEnhancer)
const store = createStore(rootReducer, undefined, composedEnhancers)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
このアプローチの問題点
このコードは動作しますが、一般的なアプリには理想的ではありません。
ほとんどのアプリは複数のミドルウェアを使用しており、各ミドルウェアには初期設定が必要になることがよくあります。index.js
に追加される余分なノイズは、ロジックがきちんと整理されていないため、すぐにメンテナンスが困難になる可能性があります。
解決策: configureStore
この問題の解決策は、ストア作成ロジックをカプセル化する新しい configureStore
関数を作成することです。この関数は、拡張性を容易にするために、独自のファイルに配置できます。
最終的な目標は、index.js
が次のようになることです。
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import configureStore from './configureStore'
const store = configureStore()
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
リデューサー、ミドルウェア、エンハンサーのインポートなど、ストアの設定に関連するすべてのロジックは、専用ファイルで処理されます。
これを実現するために、configureStore
関数は次のようになります。
import { applyMiddleware, compose, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'
import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'
export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = compose(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
return store
}
この関数は、上記と同じ手順に従いますが、拡張の準備としてロジックの一部が分割されており、将来的に追加しやすくなります。
middlewares
とenhancers
の両方が、それらを消費する関数とは別に、配列として定義されています。これにより、さまざまな条件に基づいて、より多くのミドルウェアまたはエンハンサーを簡単に追加できます。
たとえば、開発モードの場合にのみ一部のミドルウェアを追加するのが一般的であり、これは if ステートメント内でミドルウェア配列にプッシュすることで簡単に実現できます。
if (process.env.NODE_ENV === 'development') {
middlewares.push(secretMiddleware)
}後で追加する場合に備えて、
preloadedState
変数がcreateStore
に渡されます。
これにより、createStore
関数を推論しやすくなります。各ステップが明確に分離されているため、何が起こっているかをより明確に把握できます。
devtools 拡張機能の統合
アプリに追加したい一般的な機能のもう 1 つは、redux-devtools-extension
の統合です。
この拡張機能は、Redux ストアを完全に制御できる一連のツールです。これにより、アクションの検査と再生、さまざまな時点での状態の調査、ストアへのアクションの直接ディスパッチなどが可能になります。利用可能な機能の詳細については、こちらをクリックしてください。
拡張機能を統合する方法はいくつかありますが、最も便利なオプションを使用します。
まず、npm を介してパッケージをインストールします。
npm install --save-dev redux-devtools-extension
次に、redux
からインポートした compose
関数を削除し、redux-devtools-extension
からインポートした新しい composeWithDevTools
関数に置き換えます。
最終的なコードは次のようになります。
import { applyMiddleware, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'
export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = composeWithDevTools(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
return store
}
そして、それだけです。
devtools 拡張機能がインストールされたブラウザ経由でアプリにアクセスすると、強力な新しいツールを使用して探索とデバッグを行うことができます。
ホットリローディング
開発プロセスをより直感的にすることができるもう 1 つの強力なツールはホットリローディングです。これは、アプリ全体を再起動せずにコードの一部を置き換えることを意味します。
たとえば、アプリを実行し、しばらく操作した後、リデューサーの 1 つに変更を加えることにした場合に何が起こるかを考えてみましょう。通常、これらの変更を加えると、アプリが再起動し、Redux の状態が初期値に戻ります。
ホットモジュールリローディングを有効にすると、変更したリデューサーのみがリロードされるため、毎回状態をリセットせずにコードを変更できます。これにより、開発プロセスが大幅に高速化されます。
Redux リデューサーと React コンポーネントの両方にホットリローディングを追加します。
まず、configureStore
関数に追加しましょう。
import { applyMiddleware, compose, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk'
import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'
export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = compose(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
}
return store
}
新しいコードは if
ステートメントでラップされているため、アプリが本番モードではなく、module.hot
機能が利用可能な場合にのみ実行されます。
Webpack や Parcel などのバンドラーは、どのモジュールをホットリロードするか、モジュールが変更されたときに何が起こるかを指定する module.hot.accept
メソッドをサポートしています。この場合、./reducers
モジュールを監視し、更新された rootReducer
を変更時に store.replaceReducer
メソッドに渡しています。
index.js
でも同じパターンを使用して、React コンポーネントに対する変更をホットリロードします。
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import configureStore from './configureStore'
const store = configureStore()
const renderApp = () =>
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./components/App', renderApp)
}
renderApp()
ここでの唯一の追加の変更は、アプリのレンダリングを新しい renderApp
関数にカプセル化し、アプリを再レンダリングするために呼び出すようにしたことです。
Redux Toolkit によるセットアップの簡素化
Redux コアライブラリは、意図的に無偏向です。ストアの設定、状態に含まれるもの、リデューサーの構築方法など、すべてをどのように処理するかを自分で決定できます。
柔軟性が得られるため、これは一部の場合には優れていますが、その柔軟性は常に必要とされるわけではありません。場合によっては、すぐに使用できる優れたデフォルトの動作を備えた、可能な限り簡単な開始方法が必要なだけです。
Redux Toolkit パッケージは、ストアの設定など、いくつかの一般的な Redux のユースケースを簡素化するために設計されています。ストアの設定プロセスを改善するのにどのように役立つかを見てみましょう。
Redux Toolkit には、前の例で示したような、事前に構築されたconfigureStore
関数が含まれています。
最も速い使用方法は、ルートリデューサー関数を渡すだけです。
import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducers'
const store = configureStore({
reducer: rootReducer
})
export default store
渡す内容を明確にするために、名前付きパラメータを持つオブジェクトを受け入れることに注意してください。
デフォルトでは、Redux Toolkit の configureStore
は次のようになります。
redux-thunk
を含むデフォルトのミドルウェアリスト、および状態の突然変異などの一般的な間違いをキャッチする開発専用のミドルウェアを使用して、applyMiddleware
を呼び出します。- Redux DevTools Extension を設定するために
composeWithDevTools
を呼び出します。
Redux Toolkit を使用したホットリローディングの例を次に示します。
import { configureStore } from '@reduxjs/toolkit'
import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'
export default function configureAppStore(preloadedState) {
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware().prepend(loggerMiddleware),
preloadedState,
enhancers: [monitorReducersEnhancer]
})
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
}
return store
}
これにより、セットアッププロセスの一部が確実に簡素化されます。
次のステップ
ストアの設定を保守しやすくするためにカプセル化する方法を学んだので、Redux Toolkit の configureStore
APIを確認するか、Redux エコシステムで利用可能な拡張機能の一部を詳しく見てください。