JavaScript、React、Reduxにおける「レデューサー」と呼ばれる理由を分かりやすく解説
Reduxにおけるレデューサーが「レデューサー」と呼ばれる理由
関数としての役割
レデューサーは、状態とアクションを引数として受け取り、新しい状態を返す純粋関数です。この動作は、JavaScriptの配列処理メソッドである「reduce」と似ています。
この類似性から、状態を更新・生成するレデューサー関数は「レデューサー」と名付けられたのです。
状態を「圧縮」するイメージ
レデューサーは、アクションによって変化した状態を、より簡潔な表現へと「圧縮」するようなイメージで捉えることもできます。
Reduxにおける状態管理は、逐次的な状態変化を履歴として保持します。しかし、常にすべての履歴を保持するのは非効率的です。そこで、レデューサーは、現在の状態とアクション情報のみを用いて、必要な情報だけを圧縮した新しい状態を生成します。
この「圧縮」という観点からも、レデューサーという名称が適切であると言えます。
const initialState = {
todos: []
};
const todoReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, action.payload]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
)
};
default:
return state;
}
};
export default todoReducer;
このコードでは、以下の処理が行われています。
initialState
には、初期状態として空のtodos
配列を定義します。todoReducer
関数は、state
とaction
を引数として受け取り、新しい状態を返します。switch
文を使用して、アクションの種類ごとに処理を分岐します。ADD_TODO
アクションの場合、新しいTodoアイテムをtodos
配列に追加します。TOGGLE_TODO
アクションの場合、指定されたIDのTodoアイテムの完了状態を反転します。- デフォルトの処理として、現在の状態をそのまま返します。
- レデューサーは、複数のファイルを分割して記述することもできます。
- 上記のコードでは、ES6のスプレッド構文 (
...
) とアロー関数を使用しています。これらの構文は、Reduxと併用されることが多いため、理解しておくことをお勧めします。
複数の状態プロパティを更新する
レデューサーは、単一の狀態プロパティだけでなく、複数の狀態プロパティを同時に更新することができます。例として、カウンタアプリにおけるレデューサー実装を以下に示します。
const initialState = {
count: 0,
message: 'クリックしてカウントアップ'
};
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1,
message: 'カウントアップしました!'
};
case 'RESET':
return {
...state,
count: 0,
message: 'カウントをリセットしました'
};
default:
return state;
}
};
export default counterReducer;
このコードでは、INCREMENT
アクションの場合には count
プロパティと message
プロパティを同時に更新し、RESET
アクションの場合には count
プロパティを 0 にリセットしています。
ネストされた状態を更新する
レデューサーは、ネストされた状態構造を更新することもできます。例として、ショッピングカートアプリにおけるレデューサー実装を以下に示します。
const initialState = {
cart: {
items: [],
totalPrice: 0
}
};
const cartReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_ITEM':
const updatedItems = [...state.cart.items, action.payload];
const totalPrice = updatedItems.reduce((sum, item) => sum + item.price, 0);
return {
...state,
cart: {
items: updatedItems,
totalPrice
}
};
case 'REMOVE_ITEM':
const filteredItems = state.cart.items.filter(item => item.id !== action.payload);
const updatedTotalPrice = filteredItems.reduce((sum, item) => sum + item.price, 0);
return {
...state,
cart: {
items: filteredItems,
totalPrice: updatedTotalPrice
}
};
default:
return state;
}
};
export default cartReducer;
このコードでは、ADD_ITEM
アクションの場合には cart.items
配列に新しいアイテムを追加し、cart.totalPrice
プロパティを更新します。REMOVE_ITEM
アクションの場合には cart.items
配列から指定されたアイテムを削除し、cart.totalPrice
プロパティを更新します。
非同期処理を行う
レデューサーは、非同期処理を行うこともできます。ただし、非同期処理は直接的に行うのではなく、Reduxのミドルウェアを使用して行う必要があります。
例として、APIからデータを取得する非同期処理を行うレデューサー実装を以下に示します。
const initialState = {
todos: []
};
const todoReducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_TODOS':
return {
...state,
isLoading: true
};
case 'FETCH_TODOS_SUCCESS':
return {
todos: action.payload,
isLoading: false
};
case 'FETCH_TODOS_FAILURE':
return {
...state,
error: action.payload,
isLoading: false
};
default:
return state;
}
};
export default todoReducer;
このコードでは、FETCH_TODOS
アクションの場合には isLoading
プロパティを true
に設定し、非同期処理を開始します。非同期処理が成功した場合は FETCH_TODOS_SUCCESS
アクションを、失敗した場合は FETCH_TODOS_FAILURE
アクションをdispatchします。
実際の非同期処理は、Reduxのミドルウェアを使用して実装する必要があります。
Reduxレデューサーは、様々な方法で使用することができます。上記で紹介した例以外にも、様々なユースケースに対応できる柔軟な仕組みです。
- Redux チュートリアル - Redux [無効な URL を削除
javascript reactjs redux