Magic Moment 式 Redux による Store 管理
Agenda
- はじめに
- Store で管理するデータ
- Store 管理による恩恵
- Entity の重複を回避できる
- バケツリレーが減る
- Redux による Store 管理
- ディレクトリ構成
- カスタムフックを使用してコードを綺麗にする
- まとめ
- 最後に
はじめに
Magic Moment では、React/TypeScript を採用しています。
React を使っていると一番悩むのは、State をどこで管理するか、というところなのではないでしょうか?前回の React 初心者がゼロから作って苦労した State 管理の話 ではページコンポーネントに絞ってご紹介しましたが、今回は Magic Moment の考える Store 管理についてご紹介したいと思います。
Store で管理するデータ
Magic Moment では以下のものを Store 管理しています。
種類 | 詳細 |
---|---|
Entity | APIから取得するデータ |
アプリケーションの状態 | エラー情報やページ間で使用する情報など |
操作者の状態 | 認証情報やユーザーのProfileなど |
Store 管理による恩恵
Entity の重複を回避できる
Store を使うことで Entity の管理が一元化でき、データの重複を回避することができます。
重複を回避することで、以下のような編集画面などで情報を編集した際に、表示が変わる箇所と変わらない箇所が出てしまう問題が発生しなくなります。
データの持ち方は以下のようになっており、コンポーネントによるデータ管理
の場合はコンポーネントがそれぞれデータを持ち、Storeによるデータ管理
の場合はコンポーネントの外でデータを一元管理しています。
バケツリレーが減る
Store 管理することによって、親コンポーネントから受け渡しが不要になります。
受け渡しがなくなることで、無駄なレンダリングや冗長なコードを減らすことができます。
Redux による Store 管理
Store の管理方法は、useReducer
や useContext
などの hooks を使う方法と Redux
や Recoil
などのライブリを使う方法があります。
Magic Moment では、web上の情報が多い Redux
を採用することにしました。
ディレクトリ構成
ディレクトリ構成は re-ducks
パターンを採用しています。
re-ducks
パターンを採用することで、リソース毎にファイルが纏まりとても管理しやすいです。
src/store
├── ducks
│ └── user
│ ├── action.ts
│ ├── reducer.ts
│ ├── hook.ts
│ └── type.ts
├── action.ts
├── index.ts
├── reducer.ts
├── hook.ts
└── type.ts
カスタムフックを使用してコードを綺麗にする
Magic Moment は、直接 Store を参照せずカスタムフックを経由して Store にアクセスする方法を採用しています。
理由としては3つあります。
- 更新、取得、算出が一箇所で行えるようになり可読性があがる。
- useSelector/useDispatch などの Redux の hook を何度も呼び出す必要がなくなる。
- Redux 以外のライブラリへの切り替えが容易になる。
例)
/ducks/user/action.ts
APIを使用してユーザーを取得し、reducer に dispatch します。
export const listUsers = (): ThunkAction<void, RootState, undefined, RootAction> => async (dispatch) => {
const usersRes = await usersApi.usersGet();
const users = usersRes.data.body.data;
dispatch({
type: UserActionType.USER_LIST,
payload: {
users,
},
});
};
/ducks/user/hook.ts
useMemo
や useCallback
を使用して、算出したデータや Action をメモ化します。
import { listUsers } from '~/store/actions';
const useUserStore = () => {
const userState = useSelector<RootState, RootState['user']>((state) => {
return state.user;
});
const filterByStatus = React.useCallback(
(status: User['status']) => {
return userState.users.filter((user) => {
return user.status === status;
})
},
[userState.users],
);
const activeUsers = React.useMemo(() => {
return filterByStatus(UserStatus.Active);
}, [filterByStatus])
const dispatchListUsers = React.useCallback(async () => {
await dispatch(listUsers());
}, [dispatch]);
return {
userState,
filterByStatus,
activeUsers,
dispatchListUsers,
};
};
export default useUserStore;
/views/page/Example.tsx
コンポーネントでは、 useUserStore
を使用してメモ化されたデータや関数を利用します。
import { useUserStore } from '~/store/hook';
const Example = () => {
const { dispatchListUsers, activeUsers } = useUserStore();
React.useEffect(() => {
(async () => {
await dispatchListUsers();
})();
}, [dispatchListUsers]);
return (
<ul>
{
activeUsers.map((user) => {
return <li key={ user.id }>{ user.name }</li>;
})
}
</ul>
);
};
export default React.memo(Example)
まとめ
今回は Store 管理について紹介しました。
コンポーネントはUI、Store はデータ管理のように責務を分離することで、開発で生じる迷いや予期せぬバグが減ったように思います。
Store 管理に悩まれている方の参考になれば幸いです。
最後に
弊社 Magic Moment ではフロントのアーキテクチャを一緒に考えて開発してくださる方を募集しています!
8/30にはFindy様主催のイベントにMagic Momentから石田さんが登壇されます!
よろしければぜひご視聴ください!
さらに、こちらのイベントも8/29開催予定です!こちらはオンラインイベントです。Magic Momentの開発がどんなものか興味を持っていただいた方は是非ご参加ください!
Discussion