useReducer
位置づけ
通常、useReducer が useState より好ましいのは、複数の値にまたがる複雑な state ロジックがある場合や、前の state に基づいて次の state を決める必要がある場合です。また、useReducer を使えばコールバックの代わりに dispatch を下位コンポーネントに渡せるようになるため、複数階層にまたがって更新を発生させるようなコンポーネントではパフォーマンスの最適化にもなります。
ということで、場合分けの記述が出来たり、dispatchを下位コンポーネントに引き渡すことも可能とのこと。
よく見かけるreduce関数と違う
Javaとかのreduce関数とはちょっと違う。
useReducerの処理イメージ
useReducer関数により、管理対象のstate、stateを更新するdispatch関数を生成する。
dispatch関数の動作は、useReducerの第一引数reducerにて定義する。
管理対象のstateは、useReducerの第二引数、第三引数で初期値を与える/導出させる。
dispatch関数は任意のany型引数を受け取れる(=reducer関数で利用できる)。
何が良いのか
デザインパターンのストラテジーパターンを実現できる。
wikiの言葉を借りれば、
設計に優れた柔軟性をもたせることができ、かつ拡張に対して開放的であり変更に対して閉鎖的であるべきとする開放/閉鎖原則 (Open/Closed Principle, OCP) とも調和を保つことができる
言い換えれば、
- useReducerの第一引数であるreducerにstateに対する処理が閉鎖的に記述できる
- dispatch(action:any)により、どんな引数もreducerに渡せるという意味で拡張に開放的である
さらに
dipacth関数を子コンポーネントに渡せる。
useReducerの仕様
type useReducer = (reducer: reducer, initialArg: any, initializer: initializer) => dispatch: Function
type reducer = (state: object, action: any) => newState: any
type initializer = (initialArg: any) => initialState: any
※こんな書き方は出来ないがイメージ。
useReducer関数のシグネチャ
引数
- reducer関数のシグネチャを持つ関数。
- 初期state。
- オプション。初期化関数。第二パラメータを引数として起動し、初期stateを返却する関数。
返り値
- state
- dispatch関数
reducer関数のシグネチャ
引数
- 現在のstate。
- actionと呼称するanyオブジェクト。
返り値
reducerにて、計算した新たなstate
dispatch関数のシグネチャ
actionおよび現在のstateを引数にreducer関数が実行される。
引数
- actionと呼称するanyオブジェクト。
返り値
void
実装例
addとdeleteのactionを受け取れるreducerを定義してみた。
import React, { useReducer } from "react";
import "./styles.css";
export default function App() {
const initialState = { data: [] };
const [state, dispatch] = useReducer((state, action) => {
if (action.type === "add") {
return { data: state.data.concat(Math.random().toPrecision(2)) };
}
if (action.type === "delete") {
const newData = Array.from(state.data);
newData.splice(action.index, 1);
return {
data: newData
};
}
return { data: state.data };
}, initialState);
return (
<div className="App">
<button onClick={() => dispatch({ type: "add", index: 0 })}>Add</button>
<br />
{state.data.map((e, index) => {
return (
<div key={"div_" + index}>
<label>{index + ":" + e}</label>
<button
onClick={() => {
dispatch({ type: "delete", index: index });
}}
>
delete
</button>
<br />
</div>
);
})}
</div>
);
}