「useReducerについて理解を深めたい!」
「いまいちピンとこない!」
「でぃすぱっち?難しそう!」
という方に...
私なりに噛み砕いてアウトプットしてみます。(useReducerの公式リファレンス)
useReducerの使い方
他のhooksと同様、トップレベルで呼び出す。
import { useReducer } from 'react';
function reducer関数(state, action) {
switch (action.type) {
case 'アクション':
return 新しい値;
// ...
default:
throw new Error('未知のアクションタイプ');
}
}
function MyComponent() {
// 引数の数により、2パターンある
const [現在のstate, dispatch関数] = useReducer(reducer関数, 初期値);
// または
const [現在のstate, dispatch関数] = useReducer(reducer関数, 初期Arg, 初期化関数);
// ...
}
useReducerの引数
第1引数 : reducer (りでゅーさー)
- stateをどのように更新するかを指定するリデューサ関数
- リデューサ?
- 英語訳は
減速機
とか薄め液
。 - JsではArray.prototype.reduce というのがあるので、そっちの意味に近い。
- 現在のステートとアクションを受け取り、新しいステートを返す関数
- つまり、複数を1つにまとめて(縮小)新たな値を出力すること。
- 現在のステートとアクションを受け取り、新しいステートを返す関数
- 英語訳は
- アクション?
- ステートをどのように変更するかを表現するもの(チョコになっちゃえ〜といった指示)
- リデューサ?
第2引数 : initialArg 2パターン
- 初期値
- 引数が第3引数(関数)まであると、第3引数に渡す引数となる
第3引数 : init
- 初期化のための関数(後で説明)
useReducerの返り値
const [現在のstate, dispatch関数] = useReducer(第1引数, 第2引数);
1 : 現在のstate
2 : stateを別の値に更新し、レンダーを発火するための関数(dispatch関数)
dispatch関数 ?
onClick={dispatch({ type: 'チョコになっちゃえ' })}
ディスパッチされたアクションはreducer関数に渡され、その結果として新しいステートが返され、コンポーネントが再レンダリングされます
dispatch関数の引数
アクション
- stateを別の値に変化させるための関数、dispatch関数には、唯一の引数としてアクションを渡す必要がある。
-
{type : 'アクション'}
とする- アクションはuseReducerの
第1引数に設置した関数内
で使われる。(action.type)
- アクションはuseReducerの
返り値
- 無し
dispatch関数の使用サンプル
- 例 : dispatch関数は
機械を操作するリモコン
と思っておく。- 電源のオンオフ、温度の強弱の調整、前に進め、後退しろ、前に蹴れ...といった命令を飛ばす。
<button onClick={() => dispatch({ type: '電源ONOFF切り替えて' })}>電源</button>
<button onClick={() => dispatch({ type: '温度を1度上げる' })}>温度+1</button>
<button onClick={() => dispatch({ type: '風向きを自動にして' })}>風向き 自動</button>
import { useReducer } from 'react';
// 指示をうけとって、現在のstateを変更する(指示と値を混ぜて1つにする)
function reducer(state, action) {
if (action.type === '温度を1度上げる') {
// state.温度 = state.温度 + 1; returnの前に値を変更してはいけない!
return {
温度: state.温度 + 1 // returnの中で値を変更する
};
}
throw Error('不明な操作です');
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { 温度: 23 });
return (
<>
<button onClick={() => {
dispatch({ type: '温度を1上げる' }) // reducerに対して指示を出すだけ
}}>
温度を1度上げる
</button>
<p>現在の温度は {state.温度}.</p>
</>
);
}
初期状態
- 第3引数 : init : こっちも初期値だけど関数(後で説明)
引数が2つの場合
- initialStateはステートの直接的な初期値として使われます。
useReducer(関数, 初期値);
引数が3つの場合
- initFunction(initialArg)が呼ばれ、その結果が初期ステートとして使われます。
useReducer(関数, 初期Arg, 初期化関数);
// useReducer引数が3つの場合
function 初期化関数(初期Arg) {
// ここで初期値を処理(必要に応じて)
return { ...初期値, 何かのプロパティ: '何かの値' };
}
function reducer関数(state, action) {
switch (action.type) {
case '特定のアクションタイプ':
// 新しいステートを計算して返す
return { ...state, 何かのプロパティ: '新しい値' };
// ...
default:
throw new Error('未知のアクションタイプ');
}
}
function MyComponent({ 初期値 }) { // 親から受け取った初期値をreducerに使いたいとか
const [現在のstate, dispatch関数] = useReducer(reducer関数, 初期Arg, 初期化関数);
// ...
return (
<button onClick={() => { dispatch({ type: '特定のアクションタイプ' })}}>特定のアクションタイプに変更して</button>
}
useReducerの初期化時っていつなの?
下記3つを初期化時と言います。
1, ページを開いたとき(ルーティングによるページ遷移)
- ページにアクセスしたときにコンポーネントはマウントされ、初期化処理が行われます。
2, コンポーネントが動的に追加されたとき
- 例えば、ユーザーのアクションに応じて特定のコンポーネントがページに追加される場合(モーダルウィンドウの表示、リストへのアイテムの追加など)
3, 親コンポーネントが再マウントされたとき
親コンポーネントが何らかの理由でアンマウントされ、再度マウントされた時
useState と useReducer どっちを選ぶ?
行いたい状態管理が簡単か、複雑かで選べば大丈夫です。
- useState
- アクションが少なく、ロジックが単純な場合。
- 単一の状態変数を管理している場合。
- 状態が互いに独立している、関連が少ない場合
- useReducer
- フォームの複数の入力フィールド、リストの並べ替えとフィルタリング、ゲームの状態管理
- 複数のサブ値を含む場合。
- 状態の更新ロジックをコンポーネントから分離したい場合。
- 同じ状態変更ロジックを複数のコンポーネントで再利用したい場合。
- アクションによって状態の更新方法を詳細に制御したい場合。
お疲れ様でした!
少しは理解が深まれば幸いです!
より理解を深めるために、公式のサンプルソースを読み解いていきましょう!!