VALU Advent Calendar 2019 13 日目の投稿になります🎉🎉
こんにちは !! VALU サービスの Web アプリケーションにおいて,クライアントサイド開発を担当している藤本です. 6 日目の投稿 でも登場しました.今回も前回同様、頑張って書いていこうと思います!
はじめに
前回の記事で取り上げた Redux について今回も引き続き書いていこうと思っています.前回は, Reducers と Store に着目したのですが,今回は主に Actions について触れていこうと思います.
また, VALU サービスにおける Redux の扱い方は前回の記事にて少し触れたので今回は割愛しています.
dispatch について
Redux の dispatch()
( Dispatcher ) って最初わかりにくくありませんでしたか?ぼくは、 Redux を理解しようとする上で,この dispatch
にかなり苦しめられることになりました.
Redux には, dispatch と呼ばれる何かが多く出現します. 例えば,
Container から dispatch
して,Actions から dispatch
して,Store から dispatch
する.
みたいな.
...😱
たぶんこれが一番分かりやすいと思います React + Redux のフロー図解
Redux をとてもわかりやく紹介されているこの記事の「図」でも dispatch
が随所で出てきています.
もっと言うと,
Container から Actions に dispatch
して,場合によってはActions からも dispatch
して,Store から Container に dispatch
する.
日本語にしようとするとこんな感じでよくわからない感じになってしまいます.
何がわかりにくいのか
ぼくは先輩に言われていた「 Redux は非同期が難しい」の意図がわかっていませんでした.いわゆる Redux ミドルウェア問題です. VALU サービスのフロントエンド開発における非同期処理は redux-thunk
を使ってますが,わかりにくいのは Actions でも dispatch
している点だと思っています.今回はその部分について重点的に話していきたいと思います.
一旦ミドルウェアに関しては, またあとで触れるとして,「わかりにくい」 dispatch
のことを少しずつ解明していきましょう 🚀
そもそも dispatch
ってどういう意味?
ggると,発送するとか派遣するとか出てきます.
一旦簡潔に「運ぶ」にしておきましょう。
え?何を運ぶの?
それは,Actionオブジェクトです.
action: {
type: 'FETCH_HOGE',
payload: { hoge: hogehoge }
}
こんなやつですね.
なぜ Actions で dispatch
するのか
Redux ミドルウェアで, redux-thunk
を使っている場合,Actions でも, Action オブジェクトを運ぶ必要があるからです.
VALU のフロントエンド開発ではどうしているのか?
例のごとく,以下 👇 のような API からのレスポンスがあるとします.
🗂EventType.js
export type Event = {
id: number, // イベントID
title: string, // イベントタイトル
image_url?: string, // イベントのTop画像
address?: string, // イベントの開催地
geotag?: string, // イベントの位置情報
min_age: number, // 年齢制限
start_at: string, // イベントの開始時刻
end_at: string, // イベントの終了時刻
detail?: string // イベントの内容詳細
};
このレスポンスを受けるための API 通信を Actions で行います.
import { fetchEvent } from '../api/Event';
import createAsyncActions from '../utils/createAsyncActions';
export const FETCH_EVENT = 'FETCH_EVENT';
export const fetchEventCreators = createAsyncActions(FETCH_EVENT);
export const fetchEventCreator = () => dispatch => {
dispatch(fetchEventCreators.pending());
fetchEvent().then(response => {
dispatch(fetchEventCreators.fulfilled({ ...response}));
});
.catch(error => {
dispatch(fetchEventCreators.rejected(error));
});
};
このように actions/event.js
の中で dispatch
を行なっています.なぜここでも行なっているのかというと, 以下 👇 のように API 通信をおこなう際に,(当然ですが....)レスポンスの状態が Action オブジェクトとして返ってくるわけではないからです.VALU では axios
を使っているので Promise
オブジェクトになってるわけですね.
// @flow
import { apiClient } from './Api';
// ここの`Event`は上述した`EventType`を使用しているものとします
type EventResponse = {|
event: Event
|};
export const fetchEvent: () => Promise<EventResponse> = () =>
apiClient
.get<EventResponse, void>(`Endpointが入ります`)
.then(res => (res.type === 'Success' && res.data : null));
// 注意: apiClientの処理は割愛します.
では,API 通信を行なった後に再度 Action オブジェクトに変換する処理が必要になります.
上述した actions/event.js
のコードで謎の記述があったと思うのですが( createAsyncActions
です),これは,VALU の自作の関数になるのですが,それを使って Action オブジェクトを以下 👇 のように作り直します.
import { createAction } from 'redux-actions';
export default function createAsyncActions(
type,
payload = null,
meta = null
) {
return {
pending: createAction(`${type}_PENDING`, payload, meta),
fulfilled: createAction(`${type}_FULFILLED`, payload, meta),
rejected: createAction(`${type}_REJECTED`, payload, meta)
};
}
こうすることで晴れて,Action が作成されたので,それを dispatch
するわけですね..!
つまり, Actions で Redux ミドルウェアで非同期処理するために Action を作り直す必要があるため再度 dispatch
しているのです.
Actionを分割すればいいのでは? 🚀
Actions を配置するディレクトリに非同期処理が混じっていることが Redux をよりわかりにくいものにしているとぼくは考えます.
なので,非同期処理を行なっている部分とそうでない部分を単純に分けたディレクトリ構造にすればいいのではないかと考えています.
そうすれば,僕のように Actions でも dispatch
しているけど,どういうことだろう?🤔 ということになる人が減るのではないでしょうか?
この提案は, redux-thunk
を使っている場合に限るかもしれませんが,以下のようなディレクト構造に変えるとよりいいのでは?と思っております.
├── actions/
│ ├── user.js
│ └── event.js
├── middlewares/
│ ├── user.js
│ └── event.js
終わりに
Redux でも個人的には未だに難解と考えるミドルウェアについて話してみました..かなりハイカロリーだった..
これからも Redux とはうまく付き合いつつもそろそろ Hooks を使った状態管理もがっつりやっていかないとなーと考えています.
最後までありがとうございました!