🙆‍♀️

useReducerでつくるカスタムhooks

1 min read

モチベ

フォーム画面とか作ってると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

ログインするとコメントできます