今回は引き続き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あたりが少しとっつきにくかったですが、ステートの管理をかんたんにしてくれていいなと感じました。
ここまで読んでいただきありがとうございました。