その他

【React】useReduce使い方

今回は引き続きTechpitのRailsとReactでUberEats風SPAアプリケーションをつくってみよう!で学んだことのアウトプットをしていきたいと思います。

useReduceについて

useReduceはステートの管理に使うもので(state, action) => newState という型のリデューサ (reducer) を受け取り、現在の state を dispatch メソッドとペアにして返します。

const [state, dispatch] = useReducer(restaurantsReducer, initialState)

reducerの定義を実際に見てみます。reducerを定義する関数はreducersディレクトリをつくりそこに書いていきます。完成形が以下になります。

export const initialState = {
  fetchState: 'INITIAL',
  restaurantsList: [],
};

export const restaurantsReducer = (state, action) => {
  switch (action.type) {
    case 'FETCHING':
      return {
        ...state,
        fetchState: 'LOADING',
      };
    case 'FETCH_SUCCESS':
      return {
        fetchState: 'OK',
        restaurantsList: action.payload.restaurants,
      };
    default:
      throw new Error();
  }
}

reducerが何をしているかというと、ざっくりいうとステートをセットするための関数になります。ここからはそれぞれ詳しく見ていきます。

ここではreducerで管理するステートとして、fetchStateとrestaurantsListを定義します。fetchStateはデータの取得状況を表していて、restaurantsListは取得したレストランが入る想定です。

initialStateでは、文字通り最初のステートを定義します。

export const initialState = {
  fetchState: 'INITIAL'
  restaurantsList: [],
};

fetchStateはINITIAL, LOADING, OKと3つのステートがあり、取得前はINITAILとなります。

次に、定義するreducerを見ていきます。

export const restaurantsReducer = (state, action) => {
  switch (action.type) {
    case 'FETCHING':
      return {
        ...state,
        fetchState: 'LOADING',
      };
    case 'FETCH_SUCCESS':
      return {
        fetchState: 'OK',
        restaurantsList: action.payload.restaurants,
      };
    default:
      throw new Error();
  }
}

そして、reducerを(state, action) => newStateという型に従ってつくっています。引数にはactionが渡ってきているかと思いますが、これはreducerを使う側が指定したものが送られてきます。reducerではswitch ~ case文を使って、渡ってくるaction.typeによって条件分岐してそれぞれステートをセットしています。

それでは、このreducerを使う処理を見てみます。

export const Restaurants = () => {
  const [state, dispatch] = useReducer(restaurantsReducer, initialState)

  useEffect(() => {
    dispatch({ type: 'FETCHING' })

    fetchRestaurants()
    .then((data) =>
      dispatch({
        type: 'FETCH_SUCCESS',
        payload: {
          restaurants: data.restaurants
        }
      })
    )
  }, [])

  return (
    <Fragment>
      <HeaderWrapper>
        <MainLogoImage src={MainLogo} alt="main logo" />
      </HeaderWrapper>
      <MainCoverImageWrapper>
        <MainCover src={MainCoverImage} alt="main cover" />
      </MainCoverImageWrapper>

      {
        state.restaurantsList.map(restaurant =>
          <div key={restaurant.id}>
            {restaurant.name}
          </div>
        )
      }
    </Fragment>
  )
}

2行目では、最初のstateとdispatchメソッドをペアにして変数に入れています。

const [state, dispatch] = useReducer(restaurantsReducer, initialState)

dispatchメソッドは下のように書くことでreducer内で定義されたロジックに沿って整形されたステートをセットすることができます。

dispatch({ type: 'FETCHING' })

type: ‘FETCHING’の'FETCHING'の部分がaction.typeの中身に相当します。つまり、この場合action.typeが'FETCHING'のcase文が実行されることになります。case文の中ではステートをセットしてリターンしています。

その後、fetchRestaurants()でレストランの一覧がPromiseオブジェクトで返ってくるので、それに対してthenメソッドで受け止めて、そのデータをreducerに渡してステートの更新を行っています。

fetchRestaurants()
  .then((data) =>
    dispatch({
      type: 'FETCH_SUCCESS',
      payload: {
        restaurants: data.restaurants
      }
    })
  )

それでは、typeとpayloadが渡ってきているかコンソールで確認してみます。

case restaurantsActionTypes.FETCH_SUCCESS:
  console.log(action.type)
  console.log(action.payload)
  // 略

きちんと渡ってきていますね。これで、Restaurants.jsxの18行目からのreturn先で、state.restaurantsListでレストラン一覧が取得できるのが分かるかと思います。

ブラウザで表示するとこのようになります。

もぐくん
もぐくん
表示できてるね!

さいごに

useReduceの使い方についてまとめました。複雑なステートの管理ができることがuseReduceの利点ですね。dispatchあたりが少しとっつきにくかったですが、ステートの管理をかんたんにしてくれていいなと感じました。

ここまで読んでいただきありがとうございました。

ABOUT ME
酒井 駿
名古屋工業大学大学院卒業後、豊田合成(株)で品質管理を経験し、その後スタートアップ・マネーフォワードを経て、2024年11月に株式会社EGGHEAD創業。 製造業とエンジニアリング、両方の現場の知見を活かし、製造業における生成AIを活用した業務改善やシステム開発を支援します。