ReduxをREST APIと使う時の命名のコツ
概要
今回は、REST APIに対してReduxを使用する場合の命名のコツを書いていきたいと思う。
リソースにあわせてconstantsをつくる
前準備として、リクエストの状態を表すconstantsをつくっておく。
基本的にREST APIを扱う時は、ロジック側はAPIリソースに合わせた命名にしておく。こうすることで、サーバーサイドの世界とコンテキストを切り替えることなく作業できて、混乱を避けることができる。例えば、ユーザー一覧を取得する/users
というエンドポイントがあれば、users
というオブジェクトで全体を設計してゆく。
Reduxで非同期な処理を扱う時は、リクエスト中
リクエスト成功
リクエスト失敗
のような状態を持っておくと思うが、REST APIの場合、これにGET
POST
PUT
DELETE
などがのってくるので、命名で混乱しないように、下記のようなヘルパーを作っておく。
const createAsyncConstants = (constant: string): TAsyncConstants => (
{
REQUEST: constant,
SUCCESS: `${constant}_SUCCESS`,
FAILURE: `${constant}_FAILURE`,
}
);
export const createRESTConstants = (constant: string): TRESTConstants => (
{
GET: createAsyncConstants(`${constant}_GET`),
POST: createAsyncConstants(`${constant}_POST`),
PUT: createAsyncConstants(`${constant}_PUT`),
DELETE: createAsyncConstants(`${constant}_DELETE`),
}
);
export const USERS = createRESTConstants('USERS'); // 使用例
これがあると、export const USERS = createRESTConstants('USERS');
のように書くだけで、USERS.GET.REQUEST
USERS.GET.SUCCESS
USERS.GET.FAILURE
のようにそれぞれのメソッドに対応した非同期の状態を作ることができる。型がつくとサジェスチョンがでるので書き間違えることもない。
Reduxのstate設計をする
ユーザー一覧をつくるstate
は以下のようになる。
const defaultUsers: TDefaultUsers = {
items: [],
query: {
name: '',
email: '',
phoneNumber: '',
type: '',
emailVerifiedFlg: true,
page: 1,
},
totalPage: 1,
isLoading: false,
errorMessage: '',
};
注意点としては、ここでのusers
という命名は、エンドポイント(/users
)にあわせたものではないということだ。ここはビューで扱いやすいようなモデルを設計するのが正解かと思う。
例えば、エンドポイント/users
のPOST
メソッドを考えたときに、単一のユーザーを作成するフォーム画面があると想定する。
この場合、ビューには単一のユーザー情報のみが表されているので、投影するモデルは、users
ではなく、user
であり、フォームのデータはそれを構成するクエリになるのではないだろうか。state
は以下のようになりそうだ。
const defaultUser: TDefaultUser = {
query: {
name: '',
email: '',
phoneNumber: '',
type: '',
emailVerifiedFlg: true,
},
isLoading: false,
errorMessage: '',
};
ビューに対してモデル設計をすると、同じようなビューが現れた時に使いまわしができる。例えば、この場合だと、ユーザーの編集画面(/users/:id
PUT
)が似たようなビューになるかもしれない。このように、ビューとAPIリソースが一致するとは限らないため、state
はビューに対して設計した方がいい場合が多い。
actionCreator, reducerをつくる
ducksパターンでactionCreator
やreducer
を一気に作る(厳密にはもともとのducksパターンからは若干変えている部分がある)。immutableな更新については今回は省略している。
// ducksパターンをやりやすくするために`redux-actions`をいれている
import { createActions, handleActions } from 'redux-actions';
import {USERS} from './path/to/constants/';
export const usersActionCreators: TUsersActionCreators = createActions({
[USERS.GET.REQUEST]: null,
[USERS.GET.SUCCESS]: (items, totalPage) => {
return {items, totalPage};
},
[USERS.GET.FAILURE]: (errorMessage) => {
return {errorMessage};
},
});
export const usersReducer = handleActions({
[USERS.GET.REQUEST]: (state) => {
return {
...state,
isLoading: true,
errorMessage: '',
};
},
[USERS.GET.SUCCESS]: (state, {payload: {items, totalPage}}) => {
// APIレスポンスから`items`, `totalPage`をもらう
return {
...state,
items,
totalPage,
isLoading: false,
errorMessage: '',
};
},
[USERS.GET.FAILURE]: (state, { payload: { errorMessage } }) => {
return {
...state,
isLoading: false,
errorMessage,
};
},
}, defaultUsers);
上記のusersActionCreators
は、redux-actions
により、以下のようなメソッドを持つ。
export type TUsersActionCreators = {
usersGet: (query: TUsersGetQuery) => void;
usersGetSuccess: (items: Array<TUser>, totalPage: number) => void;
usersGetFailure: (errorMessage: string) => void;
}
メソッドはこちらで作成したconstants
がcamelCase
になったものが作られるため、命名としてはAPIリソースにあわせたものになるのがわかる。ポイントとしては、リクエストの状態を表すものはAPIリソースに合わせた命名をして、Redux stateは描画したいDOMにあわせて命名、モデル設計をするということである。
APIリクエストのロジック
非同期な処理をどうするかというのはredux-saga
やredux-thunk
、redux-observable
など、いくつかやり方があると思うが、個人的には責務が分離できてチームが使いやすければどれを使っても(使わなくても)いいと考える。ただし、命名は、サーバーサイドどダイレクトに通信する部分なので、APIリソースに合わせた命名をする。
実際にリクエストを行うサービスは、以下のようになる。
export const usersResource = {
get: (query: TUsersGetQuery) => {
// JS側では`camelCase`を使用しているので、`snake_case`に変換している
const q = toSnakeCase(query);
return axios.get(`${HOST}/users`, {params: q})
.then((response) => {
// 弊社では以下の指針でAPI設計しているためエラー時の処理を切り分ける
// http://blog.mmmcorp.co.jp/blog/2015/07/01/web_api_guideline/
if (response.data.status !== 200) {
return {
status: response.data.status,
message: response.data.message,
};
}
return {
status: response.data.status,
message: response.data.message,
items: response.data.users.map((user) => {
// APIレスポンスでは`snake_case`が返ってくるので`camelCase`に変換している
return toCamelCase(user);
}),
totalPage: response.data.total_page,
};
})
.catch(() => {
return {
status: 500,
message: 'サーバーエラーが発生いたしました。しばらく時間が経ってから再度お試しください。',
};
});
},
// post: () => {}
// delete: () => {}
// put: () => {}
};
/users
リソースとのやりとりは、基本的にどのメソッドでもusersResource
を通して行うというイメージだ。これが必要な箇所でそれぞれ使われる。
これらの準備ができたらあとはDOMにつなぎこむだけである。
まとめ
今回の命名指針をまとめると、以下のようになる。
- リソースにあわせて命名
- constants
- actionCreatorのメソッド
- resourceサービス
- DOMで使いやすいように命名
- Redux state
実際は、Reduxが、サーバーサイド側のリソース設計とDOMへのモデル設計の橋渡し的な役割を持つ感じである。これを応用していくと多少複雑になっても命名に迷わなくてすむ。名前が決まるとモデル設計も見えてくるので、開発のスピードも上がるかもしれない。
以上、REST APIに対してReduxを使用する場合の命名のコツを紹介しました。