LoginSignup
22
18

More than 3 years have passed since last update.

Redux開発時のディレクトリ構成のパターン

Last updated at Posted at 2020-10-04

Reduxを導入して起こるであろうファイルと記述量が多くなる問題

Reduxを勉強し始めてまず感じたのは、定型構文が多く、作るファイルも記述も多い事。

これは、実際に開発を進めるとチーム内で、共通のルールが無いとすぐにカオスな事になるなと感じました。調べたところ、その問題を解決するために、公式、非公式に、いくつかのデザインパターンが提唱されてきたようです。この記事では、その中でディレクトリ構成に絞ってまとめました。

re-ducksパターンとは

開発対象の規模にもよるのかと思いますが、個人的には、re-ducksパターンが一番整理しやすいと感じました。ファイル管理をしやすくするためのReduxのディレクトリ構成で、Ducksパターンから派生したものです。

re-ducks

reduxのディレクトリ構成例

パターン導入前の構成

Reduxの役割別にディレクトリを分けて管理しています。

action creators action types reducersは、密結合にも関わらず、異なるファイルに分割され、異なるディレクトリに分散しているため、関連性が掴みにくい構成になっています。

(基本的には、同じ名前のファイルが複数作られる構成。開発中に同じ名前のついたファイル間の移動が頻繁に起こることが予想されるため、自分が今どの役割のファイルを記述をしてるのか、混乱しやすいと思った...)

以下はsrcがルートとなります。

components
├ users.js
└ articles.js

containers
├ users.js
└ articles.js

actions
├ users.js
└ articles.js

reducers
├ users.js
└ articles.js

types
├ users.js
└ articles.js

Ducksパターン

機能ごとに分割し、一つのファイル内に、actions types reducers 等、必要な記述を全て行う。機能単位で管理するので、上記のディレクトリ構造と異なり、1ファイルを参照、または変更すれば良いというシンプルな構成。定義が1ファイルに記述するので、見通しが良くなるとは思います。

しかし、デメリットもあって、1ファイル内に必要な記述を書いてくので、開発が進むにつれて、ファイルが肥大化しやすく、閲覧性が悪くなることが容易に予想できます。

components
├ users.js
└ articles.js

containers
├ users.js
└ articles.js

modulues
├ users.js
└ articles.js

re-ducksパターン

usersやarticles等の単位でディレクトリを切って、その中に必要なファイルを分割して定義するパターンです。 Ducksパターンの1機能に1ファイルでなく、1機能に1ディレクトリという点が異なります。

密結合なファイル群が同一のディレクトリに束ねられ、ファイルごとの役割が明確になり、管理しやすくなる点がメリットとして挙げられるかと思います。

components
├ users.js
└ articles.js

containers
├ users.js
└ articles.js

users
├ actions.js
├ index.js
├ operations.js
├ reducers.js
├ selectors.js
└ type.js

articles
├ actions.js
├ index.js
├ operations.js
├ reducers.js
├ selectors.js
└ type.js

re-ducksパターンに登場する各ファイルの役割

operations

複雑な処理を書くファイルで、redux-thunk等で非同期処理を制御するようなActionは全てこのファイルに書くことになっています。

そして、最後にActionをdispatchするようになっています。
コンポーネントからActionを発行する際は、必ずoperationsファイルを経由する決まりがあります。

↓例えば、SignInの処理を書いた場合

import { signInAction } from './actions';
import { push } from 'connected-react-router';

export const signIn = () => {
  return async (dispatch, getState) => {
    const state = getState();
    const isSignedIn = state.users.isSignedIn;

    if(!isSignedIn) {
      const url = 'https://******';
      const response = await fetch(url).then(res => res.json()).catch(()=> null);

      const username = response.login;
      console.log(username);

      dispatch(signInAction({
        isSignedIn: true,
        uid: uid,
        username: username
      }))

      dispatch(push('/'))
    }
  }
}

types

TypeScriptを導入してる場合は、型定義はこのファイルで行います。

export interface ArticleState {
  title: string;
  body: string;
}

selectors

Storeで管理しているstateを参照する関数を提供します。
reselectというnpmモジュールを使います。

import { createSelector } from "reselect";

const usersSelector = (state) => state.users;

export const getUserId = createSelectore(
  [usersSelector],
  state => state.uid
);

Appコンポーネント

上記のselectorsファイル内で定義した関数をimportして使います。

useSelectorを使用して、Store内のstateを取得し、selectorsからimportした関数の引数に渡してあげます。

import React from "react";
import {useSelector} from "react-redux";
import {getUserId, getUserName} from "re-ducks/users/selectors"

const App = () => {
    const selector = useSelector(state => state)
    const uid = getUserId(selector)
    const username = getUserName(selector)

    return(
         <div>ユーザーIDは{uid}</div>
         <div>ユーザー名は{username}</div>
    )
}

export default App

各役割については、こういうものだという事は分かったが、これは実際に何度か書いて、理解を深めないとなかなか難しい印象。。。規模の大きい開発では、re-ducksを徐々に取り入れてくか、初めから意識しておいた方が良さそうな印象です。

22
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
18