[前回] Django+Reactで学ぶプログラミング基礎(33): Reduxチュートリアル(Todoアプリの実装(続き))
はじめに
前回は、TodoアプリのReducerを実装しました。
今回は、Storeの設計と実装です。
今回の内容
- Redux Store
- Storeとは
- Storeを作成
- 初期Stateをロード
- actionをdispatch
- Storeの内部構造
Storeとは
- アプリを構成するstate/action/reducerをまとめたもので、以下の機能を持つ
- 現在のアプリstateを保持
-
store.getState()
を介して現在のstateにアクセス -
store.dispatch(action)
を介してstateを更新 -
store.subscribe(listener)
を介してリスナーコールバック(listener callback
)関数を登録- コールバック関数とは
- ある関数を呼び出す際、引数として渡される別の関数のこと
- 呼び出し側で用意した関数を、呼び出し先のコードが
呼び出し返す
(callback)
- コールバック関数とは
-
store.subscribe(listener)
によって返されるunsubscribe
関数を介してリスナーの登録を解除
-
- Reduxアプリには1つのstoreしかない
- データ処理ロジックを分割する場合は、reducerを複数に分割することで実現
- 現在のアプリstateを保持
Storeを作成
- storeには単一のrootReducerが存在
- Reduxコアライブラリの
createStore
APIで、storeを作成- まず、
store.js
ファイルを追加し、createStore
とrootReducer
をインポート - 次に、
createStore
を呼び出して、rootReducerを渡す - ※
createStore
がdeprecated
と警告されましたが、一旦そのまま使います
- まず、
- Reduxコアライブラリの
src/store.js
import { createStore } from 'redux'
import rootReducer from './reducer'
const store = createStore(rootReducer)
export default store
初期stateをロード
-
createStore
の第2引数preloadedState
を用いて、store作成時の初期データを渡す- サーバーから送信されたHTMLページに含まれている値
-
localStorage
に保持され、ページに再度アクセス時に参照される値
# preloadedStateの使用例
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)
actionをdispatch
-
store.dispatch(action)
を呼び出すと、下記処理が行われる- storeが
rootReducer(state, action)
をコール - rootReducerにインポートされているsliceReducerをコール
todosReducer(state.todos, action)
- storeに新しいstate値を保存
- storeに登録されたリスナーコールバックをコール
- リスナーからは、
store.getState()
をコールし、最新stateを参照
- リスナーからは、
- storeが
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import './api/server'
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' })
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
作成したstoreが機能するかテスト
- UIがなくても、ブラウザのデベロッパーツールで更新ロジックをテスト可能
- VS Codeのターミナルで、アプリを起動すると、ブラウザが開かれる
C:\kanban\todo>cd redux-fundamentals-example-app
C:\kanban\todo\redux-fundamentals-example-app>npm start
- ブラウザで、デベロッパーツールを開き(F12)、コンソールの出力を確認
Storeの内部構造
- Redux Store実装コードのミニチュア版
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 }
}
- storeには、現在のstateとreducer関数が存在
-
getState
は現在のstateを返す -
subscribe
は、リスナーコールバックの配列を保持し、新しいコールバックを削除するための関数を返す -
dispatch
は、reducerを呼び出し、stateを保存し、listenerを実行 - store起動時に、1つのactionをdispatchし、reducerをそのstateで初期化
- store APIは、内部に
{dispatch、subscribe、getState}
を持つオブジェクト-
getState
でstateを取得可能 - state値を直接書き換えたり、配列の
sort()
関数を使ってはいけない-
array.sort()
は、既存の配列要素を書き換える(mutate
)
-
-
const state = store.getState()
// ❌ Don't do this - it mutates the current state!
state.filters.status = 'Active'
おわりに
Redux Storeの概念を深掘りし、実装を行いました。
次回も続きます。お楽しみに。