useReducerの実装方法まとめ

  • #開発
  • #React
  • #useReducer
  • #エンジニア勉強会

こんにちは、CURUCURU エンジニアの水谷です。 今月からCURUCURUエンジニアで勉強会をすることになりました。

各自でその月に悩んだこと、学習したことなどをブログでまとめつつ、それをエンジニア間で共有するという勉強会です。

今回はその勉強会でも共有する記事を書いていきます。


useReducerの実装方法まとめ

早速ですが、今回はuseReducerを使った実装方法をまとめていきます。

  • useReducerはReact hookというのはわかる
  • useStateはなんとなく理解できた
  • 構造が複雑そうでよくわからない

こういった状態のときにuseReducerを触る機会があったのですが、ようやく理解できてきたので、本記事でまとめておこうと思います。

なお、本記事の筆者はReact歴2ヶ月なので、初心者の人にも参考になると思います。

useReducerの概要

そもそもuseReducerにどういったメリットがあるかについてですが、useStateの管理では複雑になってくるときに役に立ちます。

useStateは状態を管理するときに使いますが、複数要素の状態を管理したり、状態によって処理を分けたいときなどは、useStateで書くと少々複雑な書き方になるときがあります。

それをuseReducerで解決していくという形になります。

基本的にはuseStateで管理しつつ、複雑になりそうなときにuseReducerを使うという流れで今は理解しています。

useReducerで必要なもの

useReducerで検索すると色々な書き方が出てきます。これも僕が当初混乱した理由の1つでした。

だいたいは同じ意味になるのですが、例えばuseContextと併せた内容が書いてあったり、突然Reduxの話が出てきたりする記事があったりして混乱したので、必要なものをまとめておきます。

- State
- ActionType
- Reducer

基本的に必要なものとして上記を覚えておくと良いです。CURUCURUではそれぞれ別のファイルにして管理しています。順番に書いていきます。

State

まずStateについてですが、これはその名の通りで、コンポーネントの状態を保持しているだけになります。

state.ts

export interface HogeState = {
  count: number;
};

export const initialState: HogeState = {
  count: 0;
}

初期値も併せて定義しておきます。 このコードはファイルを分けているの想定なのでexportもつけています。

ActionType

次にActionTypeについてです。 ActionTypeはいわゆる型です。どんな処理かの宣言(type)と計算ごとの引数の型(action)を定めています。

action_types.ts

export const HogeInc = Symbol('HOGE_INC');
export const HogeDec = Symbol('HOGE_DEC');

export type HogeActions = {
  type: typeof HogeInc;
  args: { hogeinc: number };
} | {
  type: typeof HogeDec;
  args: { hogedec: number }
};

Symbolで宣言することで同じ値が存在しないようになります。

Reducer

そして、Reducerです。 ActionTypeで割り振られた処理をしつつ、Stateを返す処理をします。

reducers.ts

import{ HogeInc, HogeDec } from './action_types';

import type { HogeActions } from './action_types';
import type { HogeState } from './state';

export const reducer: React.Reducer<HogeState, HogeActions> = (state, action) => {
  switch(action.type) {
    case HogeInc:
      return { count: state.count + action.args.hogeinc };
    case HogeDec:
      return { count: state.count - action.args.hogedec };
  }
}

このようにすると、action.typeの値によって処理を分けることが出来るので、返すStateの値を変えることができます。

useReducerを使った処理

では、useReducerを使うと、どういった処理になるのかを見ていきます。

import React, { useReducer } from 'react';
import * as HogeActionTypes from './action_types';
import { reducer } from './reducers'
import { initialState } from './state'

export const HogeCounter = () => {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  const onIncrement = () => {
    dispatch({
      type: HogeActionTypes.HogeInc,
      args: { hogeinc: 1 }
    });
  };
  
  const onDecrement = () => {
    dispatch({
      type: HogeActionTypes.HogeDec,
      args: { hogedec: 1 }
    })
  };

  return (
    <div className="_hogeBox">
      <div className="_hogeInner">
        <input type="button" value="inc" onClick={onIncrement} />
        <input type="button" value="dec" onClick={ondecrement} />
        <div>{state.count}</div>
      </div>
    </div>
  );
};

こういった形で処理を書けます。 ポイントはdispatchの部分で、ボタンをクリックしたときに、インクリメントかデクリメントか処理を割り振って、状態を管理することが出来る点です。

  • dispatchの部分で引数をActionTypeに渡す。
  • 渡した引数に応じてreducerが処理をする。

つまり、この関数コンポーネントの中では、actiontypetypeとargsを渡すことで、そこから先はreducerが処理をしてくれます。 これによって、関数コンポーネントの中はきれいにコードが書くことが出来る点もuseReducerの魅力かなと思います。

今回はcountというStateが1つだけなので、useStateで良いかもしれないですが、他にStateが増えてきたりして複雑になってきたら威力を発揮すると思います。

最後に

というわけで、今回はuseReducerに関してまとめてみました。 当初全く分からなかった状態から見れば、まあわりと理解できてきたかなと思います。良い先輩たちのおかげですね。

引き続きスキルアップしていきたいです。 以上、CURUCURUエンジニアの水谷でした。

メンバー募集

CURUCURUでは開発メンバーを募集中です。 CURUCURUの開発に興味があったり、モダンな開発環境で挑戦してみたいという方がいましたら、ぜひこちらも覗いてみてください!