はじめに
Reactの状態管理のためのフックとして、useState
とuseReducer
の2つがあります。
2つの違いがいまいちピンときておらず、なんとなくuseState
を使えばいいのかな、と思っていました。
なんとなくの理解から確実に自分の知識とするために、それぞれの詳細を確認し、その違いを理解していきます。
useState
基本の形
const [state, setState] = useState(init);
引数
init
:
stateに設定したい初期値。どんな型の値でもよい。関数も可。
戻り値
戻り値は配列で渡されるため、受け取る際は分割代入で受け取るのが一般的。
また、その際の変数名は何でもよいが、状態xxxx
に対して、関数はsetXxxx
とするのが一般的らしい。
state
:
現在の状態。一番初めはinit
で設定した値が入る。
init
に関数を設定した場合は、その関数の戻り値が入る。
setState
:
新しい状態を設定するための関数。
通常、引数には新しい状態を直接指定する。
前の状態に基づいて新しい状態を設定する場合には、関数を渡すこともできる。
関数を渡す場合は以下
setState(prev => {
// do something
return newState;
});
setState
にわたす関数には、前の状態(prev
)が引数として渡される。これも名前は何でもいい。
そして、新しく設定したい状態をreturnとして返す。
また、setState
を呼び出すとコンポーネントが再レンダリングされる。
使用例
import { useState } from "react";
const Example = () => {
const [count, setCount] = useState(0);
const countUp = () => setCount(prev => prev + 1);
const countDown = () => setCount(prev => prev - 1);
return (
<>
<h3>{count}</h3>
<button type="button" onClick={countUp}>count up</button>
<button type="button" onClick={countDown}>count down</button>
</>
);
};
export default Example;
[count up]ボタンを押すたび、画面上の値が1ずつインクリメントされ、[count down]ボタンを押すと画面上の値がデクリメントされます。
useReducer
基本の形
const [state, dispatch] = userReducer(reducer, init);
引数
reducer
:
state
を更新するための関数。
この関数の第一引数には、現在の状態が渡される。第二引数には後述のdispatch
に渡した引数が設定される。
新しいstate
に設定したい値を戻り値として返すようにする。
const reducer = (state, action){
switch(action.type) {
case anything:
return newState;
}
}
init
:
state
に設定したい初期値。
useReducerは第三引数として、初期値を生成する関数を設定できますが、省略可能なためここでは割愛します。
戻り値
状態管理ライブラリであるReduxの影響を受けているらしい。
そのため、更新用関数の変数名はdispatch
とするのが一般的
state
:
現在の状態。一番初めはinit
で設定した値が入る。
dispatch
:
引数で設定したreducer
を呼び出す。引数に何を設定してもよいが、オブジェクトで渡すのが一般的。
プロパティとしてtype
を設定し、それによって処理方法を分岐する。
使用例
import { useReducer } from "react";
const reducer = (state, action) => {
switch (action.type) {
case '+':
return state + 1;
case '-':
return state - 1;
default:
return state;
}
};
const Example = () => {
const [count, dispatch] = useReducer(reducer, 0);
const countUp = () => dispatch({ type: '+' });
const countDown = () => dispatch({ type: '-' });
return (
<>
<h3>{count}</h3>
<button type="button" onClick={countUp}>count up</button>
<button type="button" onClick={countDown}>count down</button>
</>
);
};
export default Example;
実行結果はuseState
のときと同じ
2つの共通点
useState
, useReducer
の両者とも
- 現在の状態を持つ
- 状態を更新する
- 状態の更新時、コンポーネントを再レンダリングする
ということが共通しています。
そのため、画面の操作によって画面上の値などを変化させたい場合、どちらを使ってもよいように思えます。
2つの相違点
決定的な違いは、更新の方法を決めるタイミングです。
useStateの場合
useState
の戻り値である更新用関数のsetState
を実行するときにstate
をどのように更新するかを決定します。
つまり、コンポーネントの内部で更新方法を決定します。
useReducerの場合
useReducer
にreducer
として関数を渡し、state
をどのように更新するかを決定します。
reducer
はコンポーネントの外部で設定することも可能です。
useReducer
の戻り値である更新用の関数dispatch
を実行する際には、すでにreducer
によって更新方法が決定されているため、単にアクションを引数として渡すだけです。
どちらを使うべきか
基本的にはuseState
でよいと考えています。
しかし、複雑なロジックや、互いに関連する状態を管理する場合にはuseReducer
のほうがわかりやすくなると思います。
以下のように、入力された項目と、カウント数を表示する画面があるとします。
これをuseReducer
を使って表現すると以下のようになります。
import { useReducer } from "react";
const ACTIONS = {
PLUS: 'PLUS'
, MINUS: 'MINUS'
, CHANGE_TXT: 'CHANGE_TXT'
}
const INIT_DATA = { text: 'apple', count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'PLUS':
return { ...state, count: state.count + 1 };
case 'MINUS':
return { ...state, count: state.count - 1 };
case 'CHANGE_TXT':
return { ...state, text: action.text };
default:
return state;
}
};
const Example = () => {
const [state, dispatch] = useReducer(reducer, INIT_DATA);
const countUp = () => dispatch({ type: ACTIONS.PLUS });
const countDown = () => dispatch({ type: ACTIONS.MINUS });
const changeText = (e) => dispatch({ type: ACTIONS.CHANGE_TXT, text: e.target.value })
return (
<>
<h3>{state.text}:{state.count}</h3>
<button type="button" onClick={countUp}>+</button>
<button type="button" onClick={countDown}>-</button>
<input type="text" value={state.text} onChange={changeText}></input>
</>
);
};
export default Example;
カウントの増減、テキストの変更の処理がすべてreducer
の中にまとまっており、シンプルです。
同じ処理をuseState
を使って実装すると、更新用関数であるsetState
の呼び出し時に処理を記載することになるため、処理がバラバラの場所に記載されます。
まとめ
useState
とuseReducer
について、基本的には実現できることは変わらないことがわかりました。
通常はだいたいuseState
でよさそうですが、複雑な処理や複数の状態を管理する場合には、useReducer
の方がより理解しやすいといえます。