🙆♀️
useReducerでつくるカスタムhooks
モチベ
フォーム画面とか作ってるとuseStateが増えてきて面倒ですよね。スプレッド構文使って、
const [state, setState] = useState({a: 'a', b: 'b', c: 'c' ........})
const handleClickC = () => setState({...state, c: state.c + 'c'})
みたいな書き方もできるけど、毎回「...state」書くのはしんどい。
だいたいの人はここでuseReducerを検討すると思います。
検索するとよくヒットしたのは
dispatch({type: '...', action: '...'})
みたいな書き方なのですが、私はuseStateの延長みたいな使い方をしたかったため、以下のような実装でカスタムhooksに隠蔽したいと思いました。
実装
簡単のため、ここでは、座標を表示するアプリについて考えます。
「Partial」を使うことで、更新するプロパティだけ指定すれば良いようにしました。
//pages/index.tsx
type Axis = { x: number; y: number; z: number };
const Index = () => {
const [{x, y, z}, setAxis]= useAxis<Axis>({x: 0, y: 0, z: 0})
return (
<>
<p>x: {x} <button onClick={() => setAxis({x: x + 1})}>+1</button></p>
<p>y: {y} <button onClick={() => setAxis({y: y + 1})}>+1</button></p>
<p>z: {z} <button onClick={() => setAxis({z: z + 1})}>+1</button></p>
<p><button onClick={() => setAxis({x: 1, y: 2, z: 3})}>to (1,2,3)</button></p>
<p><button onClick={() => setAxis('reset')}>to (0,0,0)</button></p>
</>
);
};
// hooks/useAxis.ts
type Action<T extends Object> = Partial<T> | 'reset';
export const useAxis = <T extends Object>(init: T) =>
useReducer((state: T, action: Action<T>) => {
if (action === 'reset') return init;
return { ...state, ...action };
}, init);
最初は
type Action<T extends Object> =
| { type: 'update'; action: Partial<T> }
| { type: 'reset' };
setAxis({type: 'update', action: {x: 1, y: 2, z: 3}})
setAxis({type: 'reset'})
としていましたが、思いのほか「{type: 'reset'}」と書くのが面倒だったため、このような形に落ち着きました。ここについては賛否両論あるかと思います。
まとめ
実装をしてみると大した事ないように感じますが、結構便利でした。
Discussion