useReducer をもっと自由に
皆さん、useReducer
は活用していますか?
useState
で十分と思っている場合でも useReducer
に置き換えることで、コードがシンプルかつ、わかりやすくなります。
そのためには、Reduxの呪縛を解き払ってください。useReducer
に Action type は必要ないですし、Flux Standard Action も必要ありません。また、今回はdispatch
もdispatch
らしい使い方はしていません。
つまり、Redux と同じ使い方をする必要はありません。
const [state, dispatch] = useReducer((state, action) => {
switch(action.type) {
case 'FOO_ACTION':
return { ...state, foo: action.foo }
case 'BAR_ACTION':
...
useReducer 活用例
では、useReducer
を使用すると、どのように変わるのでしょうか。簡単なサンプルコードをもとに紹介します。
【追記】下記の例は、Reactではうまく動作しません1(preactでは動作します)。後日差し替え予定ですが、イメージのつかみやすさから一旦この例のまま紹介します。
const { value, onChange } = useInput()
return (
<input type="text" value={value} onChange={onChange} />
)
上記はなんの変哲もないテキストフィールドです。
これに合うカスタム Hooks (useInput
) を、useState
と useReducer
でそれぞれ作成します。
useState の場合
export const useInput = () => {
const [value, setValue] = useState('')
const onChange = useCallback((event) => {
setValue(event.currentTarget.value)
}, [])
return { value, onChange }
}
useState
を使用した場合、Hooks は2つ使用します。今回の用途では useCallback
はオーバーキル感がありますが、他コンポーネントや他 Hooks で使用する可能性がありますので、メモ化しておくのが良いでしょう。
いずれにしても、onChange
関数を作成するには setValue
をラップします。
useReducer の場合
const inputAction = (state, event) =>
event.currentTarget.value
export const useInput = () => {
const [value, onChange] = useReducer(inputAction, '')
return { value, onChange }
}
useState
を使用した場合よりも、シンプルになりました。
useReducer
で記述するメリットは下記の2つです。
- 状態とそれを更新する関数が、ワンセットになる
- reducer 部分は純粋関数であり、Hooks やコンポーネントの外に出せる
1つ目について、setValue
のような中間の Setter が生まれませんし、用途に合わせる関数(onChange
)を別途作る必要もありません。
また、2つ目の関数外部化は、テストがしやすくなるだけでなく、無駄なオブジェクトを生成しないというパフォーマンス面のメリットもあります。
まとめ
useReducer
を使用する場合、Redux 等の使い方に縛られる必要はありません。
単純な state でも useReducer
に置き換えることで、コードがシンプルかつ、わかりやすくなり、テスタビリティがあがってパフォーマンスも上がります!
-
React では、DOM の Event を独自オブジェクト(
SyntheticEvent
)で wrap しているのですが、それの有効期限がuseReducer
の処理実行タイミングまで生きていないのが原因です。 ↩