noah.plus

Re-ducksパターン:React + Redux のディレクトリ構成ベストプラクティス

2018-09-30

Re-ducks というディレクトリ構成のベストプラクティスについてまとめる。Re-ducksパターンを使うことで、React + Redux を用いた開発がよりメンテナンスしやすいものになる。

Ducks パターン

名前からもわかるように、Re-ducks は Ducks というパターンをベースにしている。

erikras/ducks-modular-redux

Ducks パターンが解決すること: actionType、action、reducerが散らばっててつらい

結局のところ actionType、action、reducer は密結合なので、ducksパターンではこれら3つを1つのファイルにまとめる。

// widget.js

// Actions
const LOAD   = 'my-app/widgets/LOAD';
const CREATE = 'my-app/widgets/CREATE';
const UPDATE = 'my-app/widgets/UPDATE';
const REMOVE = 'my-app/widgets/REMOVE';

// Reducer
export default function reducer(state = {}, action = {}) {
  switch (action.type) {
    // do reducer stuff
    default: return state;
  }
}

// Action Creators
export function loadWidgets() {
  return { type: LOAD };
}

export function createWidget(widget) {
  return { type: CREATE, widget };
}

export function updateWidget(widget) {
  return { type: UPDATE, widget };
}

export function removeWidget(widget) {
  return { type: REMOVE, widget };
}

// side effects, only as applicable
// e.g. thunks, epics, etc
export function getWidget () {
  return dispatch => get('/widget').then(widget => dispatch(updateWidget(widget)))
}

ducksパターンではこのようなファイルを module と呼ぶ。

module を作る上で、いくつかのルールがあるのでこれを守る。

  • moduleは reducer() という名前の関数を export default しなくてはならない
  • moduleの action creator をは関数として export されなくてはならない
  • moduleは action type を npm-module-or-app/reducer/ACTION_TYPE という形で持たなくてはならない
  • moduleは action type が他のディレクトリで必要ならば UPPER_SNAKE_CASE の形で export できる。

一般的な Redux のディレクトリ構造と、ducksパターンを比較すると以下のようになる。

// 一般的なディレクトリ構造

actions
├ articles.js
├ comments.js
└ users.js

reducers
├ articles.js
├ comments.js
└ users.js

types
├ articles.js
├ comments.js
└ users.js
// ducksパターン

modules
├ articles.js
├ comments.js
└ users.js

もともと関数の種類によって分類されていたファイルが、ducksパターンでは機能ごとに分類されるようになった。バラバラになっていたファイルがまとまり、とてもわかりやすい。

Re-ducks パターン

Ducksパターンおかげでディレクトリ問題が解決されたかに思われた。しかし開発の規模が大きくなるにつれて、しんどさが増していく……。

アプリが中・大規模になると、1つに集約された module ファイルが読みづらく、メンテナンスしにくいものになってしまう。この問題へのアプローチとして Re-ducksパターンが生まれた。

alexnm/re-ducks

Re-ducksパターンが解決すること:ducksパターンにおける module がだんだん肥大化していってつらい

Re-ducksパターンでは以下のようなディレクトリ構造になる。

ducks
├ articles
│   ├ index.js
│   ├ types.js
│   ├ actions.js
│   ├ reducers.js
│   ├ operations.js
│   └ selecors.js
│
├ comments
│   ├ index.js
│   ├ types.js
│   ├ actions.js
│   ├ reducers.js
│   ├ operations.js
│   └ selecors.js
│
└ users
    ├ index.js
    ├ types.js
    ├ actions.js
    ├ reducers.js
    ├ operations.js
    └ selecors.js

Re-ducksパターンでは機能ごとにディレクトリを分け、その中で関数の種類ごとにファイルを分ける。つまりducksパターンの「機能ごとの分類」と、一般的なReduxの「関数の種類ごとの分類」をいい塩梅で組み合わせている。

ちなみにディレクトリ分けの基準になる「機能」は、見た目で分類するよりもデータで分類した方が無理がないとのこと。

参考:React/Reduxで 秩序あるコードを書く

Re-ducksパターンでは、ducksパターンと比べてファイルの数がかなり多い。しかし、それぞれのファイルが果たす役割が明確なので思いのほかシンプル。

ducksパターンにおける moduleファイルは、Re-ducksパターンの index.js に当たる。ここで他のファイルをまとめ上げ、利用する際のインターフェースにする。作成時には、ducksパターンと同じルールを守らなくてはならない。

types.jsactions.jsreducers.js の3つは一般的なReduxのディレクトリ構造でも別々のファイルとして作成される。ducksパターンでは1つの moduleファイルとしてまとめられたが、Re-ducksパターンでは結局バラバラのファイルになった。

Re-ducksパターン固有のファイルとして、operations.jsselectors.js の2つがある。これらのおかげで、state をシンプルに保ったまま複雑な処理を実装できる。

  • Operations

    • operations とは、1つ以上の action を組み合わせたもの。個別の action は単純なままに、必要に応じて複雑な operations を作成できる。redux-thunkなどのミドルウェアはここで使う。
  • Selectors

    • selectors とは、state から必要な値を算出する関数のこと。state をシンプルに保つために、既存の state から算出できる値はすべて Selectors 経由で取得する。

おわりに

Re-ducksパターンを最初に目にしたときはファイル数の多さに戸惑った。しかし、ducksパターン → Re-ducksパターンの流れを把握したら腑に落ちた。

Re-ducksパターンの実装例としては以下の example がとても参考になった。文字であれこれと語るより、コードを追った方がすんなり理解できるかも。

jthegedus/re-ducks-examples

また、Re-ducksの発案者が書いた記事がmediumにあるので、迷った時にはこれに立ち返れば間違いがない。

Scaling your Redux App with ducks


noah.plus