1. はじめに:Reduxとは何か
ReduxはJavaScriptアプリケーションにおける「状態管理」を効率化するためのライブラリです。特にReactと組み合わせて使われることが多いですが、React専用ではなく、VueやAngularなど他のフレームワークとも連携できます。Reduxを導入することで、アプリケーション全体の状態(データ)を一元管理し、どこからでも状態の参照や更新が可能になります。これにより、コンポーネント間のデータ共有が容易になり、データフローが明確化され、バグの発生やデバッグの難しさを大きく減らすことができます。
// CDN経由でReduxを利用する例
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
2. Reduxの3つの基本原則
Reduxには「唯一の情報源(Single Source of Truth)」「状態は読み取り専用(State is Read-Only)」「変更は純粋な関数で(Changes are made with pure functions)」という3つの基本原則があります。全ての状態は一つのストアで管理され、状態の変更は必ずアクションを通して行われます。状態の更新はリデューサーという純粋関数で定義され、副作用を排除し予測可能なアプリケーションを実現します。
// シンプルなReducer
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
3. Reduxの主要コンポーネント:Store, Action, Reducer
Reduxの中心は「Store(状態の保管場所)」「Action(状態変更の命令)」「Reducer(状態をどう変えるかのルール)」の3つです。Storeはアプリ全体の状態を保持し、Actionは何が起きたかを表すオブジェクト、Reducerは現在の状態とActionを受け取り新しい状態を返します。この3つの役割をしっかり理解することがReduxの第一歩です。
const store = Redux.createStore(counter)
store.dispatch({ type: 'INCREMENT' }) // 状態が1増加
4. 最小構成のカウンターアプリ
Reduxの基本を学ぶにはカウンターアプリが最適です。Reducerで状態遷移を定義し、Storeを作成し、Actionをdispatchすることで状態が変化します。UIは素のJavaScriptやReactなどで自由に構築できます。
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
</head>
<body>
<div>
<span id="value">0</span>
<button id="increment">+1</button>
<button id="decrement">-1</button>
</div>
<script>
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT': return state + 1
case 'DECREMENT': return state - 1
default: return state
}
}
const store = Redux.createStore(counter)
const valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState()
}
render()
store.subscribe(render)
document.getElementById('increment').onclick = () => store.dispatch({ type: 'INCREMENT' })
document.getElementById('decrement').onclick = () => store.dispatch({ type: 'DECREMENT' })
</script>
</body>
</html>
5. ActionとAction Creatorの役割
Actionは状態変更の命令書であり、typeプロパティを必ず持ちます。Action CreatorはActionを生成する関数で、複雑なアクションやパラメータ付きアクションの管理に役立ちます。
function increment() {
return { type: 'INCREMENT' }
}
function decrement() {
return { type: 'DECREMENT' }
}
store.dispatch(increment())
store.dispatch(decrement())
6. 複数ReducerとcombineReducers
アプリが大きくなると状態管理も複雑になります。Reduxでは複数のReducerをcombineReducersで合成し、状態を分割して管理できます。これにより、大規模アプリでも見通しの良い設計が可能です。
const rootReducer = Redux.combineReducers({
counter: counterReducer,
todos: todosReducer
})
const store = Redux.createStore(rootReducer)
7. Reactとの連携(react-redux)
ReactでReduxを使う場合は、react-reduxパッケージを利用します。Providerでアプリ全体を包み、useSelectorやuseDispatchフックで状態やアクションを簡単に扱えます。
import React from 'react'
import { Provider, useSelector, useDispatch } from 'react-redux'
import store from './store'
function App() {
const count = useSelector(state => state.count)
const dispatch = useDispatch()
return (
<div>
<h1>カウント: {count}</h1>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
</div>
)
}
// index.jsで
import ReactDOM from 'react-dom'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
8. Redux Toolkitによる最新開発
Redux Toolkitは、Reduxの公式推奨ツールで、ボイラープレートを減らし、より簡単に安全にReduxを使えるようにします。createSliceやconfigureStoreを使えば、ReducerやActionの自動生成も可能です。
import { configureStore, createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1 },
decrement: state => { state.value -= 1 }
}
})
export const { increment, decrement } = counterSlice.actions
export default configureStore({ reducer: { counter: counterSlice.reducer } })
9. 非同期処理とRedux Thunk
API通信など非同期処理にはRedux Thunkがよく使われます。ThunkはAction Creatorが関数を返すことで、dispatchのタイミングや複数回のdispatchを制御できます。
import thunk from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
const store = createStore(reducer, applyMiddleware(thunk))
function fetchData() {
return async dispatch => {
dispatch({ type: 'FETCH_START' })
const res = await fetch('/api/data')
const data = await res.json()
dispatch({ type: 'FETCH_SUCCESS', payload: data })
}
}
store.dispatch(fetchData())
10. Middlewareの活用
ReduxのMiddlewareは、ActionとReducerの間に独自の処理を挟む仕組みです。ロギング、非同期処理、エラー監視など様々な拡張が可能です。
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
const store = Redux.createStore(reducer, Redux.applyMiddleware(logger))
11. 状態の永続化とlocalStorage連携
Reduxの状態をlocalStorageなどに保存することで、ページリロード後も状態を維持できます。subscribeで状態変更時に保存し、初期値として読み込むことで実現します。
const persistedState = JSON.parse(localStorage.getItem('state')) || undefined
const store = Redux.createStore(reducer, persistedState)
store.subscribe(() => {
localStorage.setItem('state', JSON.stringify(store.getState()))
})
12. Redux DevToolsによるデバッグ
Redux DevToolsは、状態の変化やActionの履歴を可視化できる強力なデバッグツールです。開発時にストア作成時に組み込むだけで利用できます。
const store = Redux.createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
13. 実践:TODOアプリのRedux実装例
Reduxの実力を体感するには、TODOアプリのような複数データの管理が最適です。タスク追加・完了切り替えなどをReducerとActionで管理し、状態の一元化とデータフローの明確化を実感できます。
// reducer.js
const initialState = { tasks: [] }
function todoReducer(state = initialState, action) {
switch (action.type) {
case 'ADD_TASK':
return { ...state, tasks: [...state.tasks, { text: action.text, done: false }] }
case 'TOGGLE_TASK':
return {
...state,
tasks: state.tasks.map((t, i) =>
i === action.index ? { ...t, done: !t.done } : t
)
}
default:
return state
}
}
export default todoReducer
14. Reduxのメリットと注意点
Reduxは状態管理を一元化し、予測可能で安定したアプリケーションを実現します。大規模開発や複雑なデータフローのあるアプリで特に威力を発揮しますが、小規模アプリでは導入コストが高くなる場合もあるため、プロジェクト規模に応じて選択しましょう。
15. まとめと今後の展望
ReduxはJavaScriptフロントエンド開発における状態管理のデファクトスタンダードです。Reactとの組み合わせはもちろん、Redux ToolkitやMiddleware、DevToolsなどのエコシステムも充実しています。今後も進化が期待されるReduxを、ぜひあなたのプロジェクトでも活用してみてください。