Reducerのススメ
React勉強中ですが、Reducerがとんでもねぇものだということを伝えたく筆を執りました。実際に使用した実感として、コンポーネント間でロジックや値、stateを共通化できるのが一番のメリットだと思っています。
あと、reducerのaction.typeを変えるだけで処理を切り替えることができるのも、何気に便利です。
ともあれ、さっそく、使ってみましょう!
実装
よく使われるbuttonコンポーネントの例でReducer使ってみます。 コードの参照はこのサイトから
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
ここで宣言しています。ざっくり左のstateにはinitializeState、dispatchにreducerを入れています。
const [state, dispatch] = useReducer(reducer, initialState);
ここで、実際にdispatchの中にあるファンクションを呼び出しています。
ちなみに、onClickの中の「()=>」を消すと、無限ループバグになるときがあるので注意です。(renderごとにdispatchの中の処理が走り、stateが更新されると、再びrenderされ、再び読み込まれたdispatchが走ります)
# 正しい
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
# 無限ループ
<button onClick={dispatch({type: 'decrement'})}>-</button>
以下は、先ほどの宣言時に入れていた値とファンクションの中身になります。
initializeStateでは値やstateを初期化しています。
reducerでは引数を受け取り、action,typeごとにそれぞれ処理を実行してくれます。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
さきほど書いたdispatchでの括弧にあるtyoeが上のaction.typeで識別する値になります
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
発展
ここから簡単に発展させられるところとして、 ・ファイル分割 ・指定するaction.typeを楽に参照できるようにする があります。ざっくりこんな感じです。const initialState = {count: 0};
# ここ追加
export const actionTypes = () {
increment:"increment",
decrement:"decrement",
}
export function reducer(state, action) {
switch (action.type) {
case actionTypes.increment:
return {count: state.count + 1};
case actionTypes.decrement:
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: actionTypes.decrement})}>-</button>
<button onClick={() => dispatch({type: actionTypes.increment})}>+</button>
</>
);
}
ここで追加したのは下記のコードです。
このコードの役割は、actiontypeで値を参照させることで、このReducerで使う単語を指定することができます。
export const actionTypes = () {
increment:"increment",
decrement:"decrement",
}
また、べた書きしていた箇所を変えられます。
以下では、case文の値をべた書きではなく、参照して値を判定しています。
export function reducer(state, action) {
switch (action.type) {
case actionTypes.increment:
return {count: state.count + 1};
case actionTypes.decrement:
return {count: state.count - 1};
default:
throw new Error();
}
}
またこちらではtypeで渡している値をべた書きではなく参照値にして、記載ミスがないようになっています。
<button onClick={() => dispatch({type: actionTypes.decrement})}>-</button>
<button onClick={() => dispatch({type: actionTypes.increment})}>+</button>
参考