概要: このチュートリアルでは、複雑な状態ロジックを処理するためにReactのuseReducer
フックを使用する方法を学びます。
React useReducer()フックの紹介
useReducer
フックは、useStateフックの代替であり、コンポーネントの状態を管理できます。
useReducer
フックは状態を生成します。- 状態を変更すると、コンポーネントの再レンダリングがトリガーされます。
useReducer
フックは、次のような場合に役立ちます。
- コンポーネントに、互いに密接に関連する複数の状態がある場合。
- 将来の状態の値が、現在の状態に依存する場合。
useReducer
フックを使用するには、次の手順に従います。
ステップ1. react
ライブラリからuseReducer
フックをインポートします
import { useReducer } from 'react';
Code language: JavaScript (javascript)
ステップ2. state
とaction
の2つのパラメータを受け取るreducer()
関数を定義します
function reducer(state, action) {
// ...
}
Code language: JavaScript (javascript)
ステップ3. コンポーネントでuseReducer
フックを使用します
function MyComponent() {
const [state, dispatch] = useReducer(reducer, initialArg, init?);
// ...
}
Code language: JavaScript (javascript)
以下は、useReducer
フックの構文です。
reducer
は、状態がどのように更新されるかを決定する関数です。initialArg
は初期状態です。init
は、初期状態を返す関数を指定するオプションのパラメータです。省略した場合、初期状態はinitialArg
に設定されます。それ以外の場合、初期状態はinit(
の呼び出し結果に設定されます。initialArg
)
useReducer()
関数は、正確に2つの値を持つ配列を返します。
state
は現在の状態です。dispatch
関数を使用すると、状態を新しい状態に更新し、再レンダリングをトリガーできます。
以下は、useReducer
フックの動作を示しています。

React useReducer()フックの例
以下は、カウンターアプリを示しています。
インクリメントボタンをクリックすると、カウントが1つ増えます。デクリメントボタンをクリックすると、カウントが1つ減ります。
ステップを変更してインクリメントまたはデクリメントボタンをクリックすると、カウントがステップ数だけ増減します。
useStateフックを使用するカウンターアプリをダウンロードしてください。
以下は、App
コンポーネントのソースコードです。
import { useState } from 'react';
import './App.css';
const App = () => {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const increment = () => setCount(count + step);
const decrement = () => setCount(count - step);
const handleChange = (e) => setStep(parseInt(e.target.value));
return (
<>
<header>
<h1>Counter</h1>
</header>
<main>
<section className="counter">
<p className="leading">{count}</p>
<div className="actions">
<button
type="button"
className="btn btn-circle"
onClick={decrement}
>
-
</button>
<button
type="button"
className="btn btn-circle"
onClick={increment}
>
+
</button>
</div>
</section>
<section className="counter-step">
<label htmlFor="step">Step</label>
<input
id="step"
type="range"
min="1"
max="10"
value={step}
onChange={handleChange}
/>
<label>{step}</label>
</section>
</main>
</>
);
};
export default App;
Code language: JavaScript (javascript)
Appコンポーネントには、2つの関連する状態があります。
- カウント
- ステップ
また、次の状態は前の状態に依存します。
const increment = () => setCount(count + step);
const decrement = () => setCount(count - step);
Code language: JavaScript (javascript)
useState
フックをuseReducer
フックに置き換えます。
ステップ1. state
とaction
の2つのパラメータを持つreducer()
関数を定義します
const reducer = (state, action) => {
// ...
};
Code language: JavaScript (javascript)
ステップ2. useState
フックを削除し、代わりにuseReducer
フックを使用します
// const [count, setCount] = useState(0);
// const [step, setStep] = useState(1);
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
Code language: JavaScript (javascript)
左側
state
は、count
とstep
を含むすべての状態変数を含むオブジェクトです。dispatch
は、setCount
関数とsetStep
関数と同等の機能です。
右側
reducer
は、state
オブジェクト内のcount
プロパティとstep
プロパティをどのように更新するかを決定する関数です。{count: 0, step: 1}
は初期状態です。
以下の図は、useState
フックとuseReducer
フックの同等性を示しています。

状態を更新したい場合は、useReducer()
関数の呼び出しから返されるdispatch()
関数を呼び出すことができます。
dispatch()
関数を呼び出すと、Reactはreducer
関数を見つけて実行します。
reducer
関数は新しい状態を返す必要があります。何も返さない場合、状態はundefined
になります。
reducer
関数は、状態を直接変更してはなりません。代わりに、更新されたプロパティを持つ状態の新しいコピーを返す必要があります。
さらに、reducer()
関数には、async/await、APIへのリクエスト、Promiseを含めたり、外部変数を変更したりすることはできません。言い換えれば、純粋関数でなければなりません。
dispatch()
関数を呼び出すときは、状態の更新方法をreducer()
関数に伝えるためのオブジェクトを渡す必要があります。
慣例により、2つのプロパティを持つaction
オブジェクトを渡す必要があります。
{type: 'ACTION', payload: value }
Code language: JavaScript (javascript)
type
は、どの状態を更新するかをreducerに伝える文字列です。payload
は、reducerに渡される値です。
action
オブジェクトでは、type
プロパティは必須ですが、payload
プロパティはオプションです。
たとえば、ユーザーがインクリメントボタンをクリックした場合、次のようにdispatch()
関数を呼び出す必要があります。
dispatch({ type: 'INCREMENT' })
Code language: JavaScript (javascript)
reducer()
関数では、actionオブジェクトをチェックし、それに応じて状態を更新できます。
if(action.type === 'INCREMENT') {
return {
...state,
count: state.count + state.step
};
}
Code language: JavaScript (javascript)
同様に、ユーザーがデクリメントボタンをクリックした場合、異なるアクションタイプでdispatch()
関数を呼び出す必要があります。
dispatch({ type: 'DECREMENT' })
Code language: JavaScript (javascript)
そして、reducer()
関数内のactionオブジェクトをチェックして、状態のcount
プロパティを減らします。
if(action.type === 'DECREMENT') {
return {
...state,
count: state.count - state.step
};
}
Code language: JavaScript (javascript)
ユーザーが状態を変更した場合、次のようにdispatch()
関数を呼び出すことができます。
dispatch({ type: 'CHANGE_STEP', payload: newStep });
Code language: JavaScript (javascript)
reducer()
関数では、それに応じて状態のstep
プロパティを更新できます。
if(action.type === 'CHANGE_STEP') {
return {
...state,
step: action.payload
};
}
Code language: JavaScript (javascript)
reducer()
関数は多くのactionタイプを処理するため、switch…caseステートメントを使用できます。
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + state.step,
};
case 'DECREMENT':
return {
...state,
count: state.count - state.step,
};
case 'CHANGE_STEP':
return {
...state,
count: action.payload,
};
default:
throw new Error(`action type ${action.type} is unexpected.`);
}
};
Code language: JavaScript (javascript)
actionタイプが決定されない場合は、エラーをスローするか、無視することができます。
以下は、useState
フックの代わりにuseReducer
フックを使用する完全なApp
コンポーネントを示しています。
import { useReducer, useState } from 'react';
import './App.css';
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + state.step,
};
case 'DECREMENT':
return {
...state,
count: state.count - state.step,
};
case 'CHANGE_STEP':
return {
...state,
step: action.payload,
};
default:
throw new Error(`action type ${action.type} is unexpected.`);
}
};
const App = () => {
// const [count, setCount] = useState(0);
// const [step, setStep] = useState(1);
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
// const increment = () => setState(count + step);
// const decrement = () => setCount(count - step);
// const handleChange = (e) => setStep(parseInt(e.target.value));
const increment = () => dispatch({ type: 'INCREMENT' });
const decrement = () => dispatch({ type: 'DECREMENT' });
const handleChange = (e) =>
dispatch({
type: 'CHANGE_STEP',
payload: parseInt(e.target.value),
});
return (
<>
<header>
<h1>Counter</h1>
</header>
<main>
<section className="counter">
<p className="leading">{state.count}</p>
<div className="actions">
<button
type="button"
className="btn btn-circle"
onClick={decrement}
>
-
</button>
<button
type="button"
className="btn btn-circle"
onClick={increment}
>
+
</button>
</div>
</section>
<section className="counter-step">
<label htmlFor="step">Step</label>
<input
id="step"
type="range"
min="1"
max="10"
value={state.step}
onChange={handleChange}
/>
<label>{state.step}</label>
</section>
</main>
</>
);
};
export default App;
Code language: JavaScript (javascript)
プロジェクトのソースコードをダウンロード
イミュータブルな状態を操作するためのImmerパッケージの使用
reducer関数では、状態を直接変更することはできませんが、変更された状態のコピーを返します。これは非常に不便です。
状態をより便利に操作するために、Immerパッケージを使用できます。したがって、次のように変更された状態のコピーを返す代わりに
return {
...state,
count: state.count + state.step,
};
Code language: JavaScript (javascript)
次のように状態を直接変更できます。
state.count = state.count + state.step;
Code language: JavaScript (javascript)
Immerパッケージを使用するには、次の手順に従います。
ステップ1. Immerパッケージをインストールします
npm install immer
Code language: JavaScript (javascript)
ステップ2. produce
関数をコンポーネントにインポートします
import { produce } from 'immer';
Code language: JavaScript (javascript)
ステップ3. 3番目に、reducer関数をproduce関数でラップします
const [state, dispatch] = useReducer(produce(reducer), { count: 0, step: 1 });
Code language: JavaScript (javascript)
ステップ4. 状態を直接変更するreducer()関数を簡略化します
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
state.count = state.count + state.step;
break;
case 'DECREMENT':
state.count = state.count - state.step;
break;
case 'CHANGE_STEP':
state.step = action.payload;
break;
default:
throw new Error(`action type ${action.type} is unexpected.`);
}
};
Code language: JavaScript (javascript)
まとめ
useReducer
フックは、useState
フックの代替です。- コンポーネントに複数の密接に関連する状態があり、将来の状態が現在の状態に依存する場合は、
useReducer
フックを使用します。 - 状態をより便利に操作するには、
Immer
パッケージを使用します。