ReduxにImmutable.JSを適用してみた

# はじめに

この記事は[DMM.com #1 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/dmm) の12/13の記事です。


[Immutable.JS](https://facebook.github.io/immutable-js/)を使って、React+Redux製WebアプリのReducerをすっきりさせた方法と結果、及び注意点についてまとめました。

# 目的

Immutable.JSとは、Facebookが開発した、不変であるデータ構造を扱うためのJavaScriptライブラリです。

開発中だったReact+Reduxアプリでは、開発を進めれば進めるほど、ActionとReducerが肥大化し、改修がしづらくなっていました。
(Reducer内にロジックが含まれていたり、冗長な`...state`を書く必要があったり…etc。)

今回は、Immutable.JSを使って、Reducerの肥大化を防ぎ、保守性・改修性を上げることを目的とします。



# 方法

今回は一般的なReact+Reduxアプリのフローに、Modelという概念を追加します。
Modelでは、actionに応じた処理を記述し、immutableなオブジェクトをReducerに返します。
また、今回は [Using Immutable.JS with Redux](http://redux.js.org/docs/recipes/UsingImmutableJS.html) に出来るだけ沿った記述を行いました。

![スクリーンショット 2017-12-12 23.12.57.png](https://qiita-image-store.s3.amazonaws.com/0/210893/c6c43074-aa5c-8566-8f8d-637e5f40af60.png)

# Immutable.JSを使う際に注意すること

## 書き方が独特

覚えてしまえば楽なのですが、Immutable.JS特有の書式を覚える必要があります。
そのため普通のReact+Reduxと比べると学習コストが少し上がります。

## パフォーマンスを重要視した記述について

パフォーマンスを重要視するなら以下の記述に注意すべし。

#### その1

```js
// これでもうごく
state.set('loading', false).set('user', user)

// さいてき
state.withMutations(s => s.set('loading', false).set('user', user))
```

Immutable.JS は、内部で何度も再編成を行いデータを構築するため、
`withMutations` を使って再編成が1回で済むような記述をすることが望ましいようです。


#### その2

```js
// これでもうごく
const mapStateToProps = (state) => ({
    loans: state.get('loans').toJS(),
})

// さいてき
const mapStateToProps = (state) => ({
    loans: state.get('loans'),
})
```

`mapStateToProps` でいちいち `toJS()` をすると、再編成する回数が増え、パフォーマンスがやや下がるようです。。


## オブジェクトの構成について
Immutable.JS でラップされたオブジェクトは、普通のオブジェクトと中身や構成がだいぶ異なるので注意。


```js
// 通常のオブジェクト
{
    type: 'test',
    message: 'fuga',
}

// Immutableなオブジェクト
Hoge {
    values: List {
        size: 2,
        __altered: true,
        __hash: undefined,
        __ownerID: undefined,
        _level: 5,
        _origin: 0,
        _root: null,
        _tail: VNode {
            array: ['test', 'fuga'], 
            ownerID: ownerID {}, 
        },
    }
}
```
値を取り出して使いたかったら `toJS()` する必要があります。
ただし `mapStateToProps()` 内で `toJS()` をするとパフォーマンスが下がります。気にする場合はHigher Order Component を介して `toJS()` を行うことで回避できます。


# 結果

## コードはどうなったか

例えば、Reducerの初期値が

```js
{
    input: {
        name: 'hoge',
        item: {
            text: 'hello, Immutable.JS!',
            type: 'fuga',
        },
    },
    data: {
        name: 'test',
        boxes: [
            {
                name: 'hoge',
                items: [],
            },
        ],
    },
};
```
であるとき、`ADD_ITEM_IN_BOX`というActionが発行された場合はどうなるか？

### before
※`redux-actions`の`handleActions()`を使用した記述をしています

```reducer.js
ADD_ITEM_IN_BOX: (state, action) => {
    const { index, item } = action.payload;
    const box = state.data.boxes[index];
    const items = [...box.items, item];
    const newBox = {
        ...box,
        items,
    };
    const newBoxes = [...state.data.boxes];
    newBoxes[index] = newBox;
    return {
        ...state,
        data: {
            ...state.data,
            boxes: newBoxes,
        },
    };
},
```


### after

```reducer.js
ADD_ITEM_IN_BOX: (state, action) => {
    return state.addItemInBox(action.payload.index, action.payload.item);
}
```
```model.js
addItemInBox(index, item) {
    const items = this.get('data').get('boxes').get(index).get('items');
    return this.setIn(['data', 'boxes', index, 'items'], items.push(fromJS(item)));
}
```

なんとコードの行数が半分以下になりました。



## 保守性はどうなったか

[plato](https://github.com/es-analysis/plato)という性能可視化ツールで可視化された解析結果を見ると、行数はアプリ全体でおよそ10000行ありましたが、そのうちの約1000行、全体の1割のコード削減に成功しました。


またReducerファイルの保守性はそれぞれ5~20%上がっており、下がっていたものはありませんでした。

Modelファイルの保守性はどれも70~90%台と高いスコアが出ていました。

これらの結果から、肥大化し改修がしづらくなっていたReducerは、Immutable.JSを適用することで改修がしやすくなり、読みやすくなった、といえるでしょう。

# 反省・感想・今後の展望

### Modelいらない説
今回は、異なるactionで処理する内容が同じであることが多かったためModelに処理を委ねていい感じにすることができましたが、
例えば1つのactionにつき処理が全て異なる、という場合は、Modelを作らずとも、Reducer内に記述を直書きしてもよいなーと思いました。（ググるとこちらの例の方が多いです）


### 超時間かかった
また、今回は開発の途中でImmutable.JSを導入したせいか、完全移行まで物凄く時間がかかってしまいました（Reducer10個、Action100個近くあるアプリで2週間くらい）。
もし取り入れるのであれば、開発の一番最初から取り入れたほうが吉です。

### Action Creatorsの圧縮
実はAction Creatorsについても、もっと短く出来るのではと思い挑戦していましたが、`redux-saga`等のmiddlewareとの兼ね合いで実現できませんでした。アプリの構成にもよりますが、これを実現するのは難しいと思いました。



# まとめ

Immutable.JSをReduxに適用することで、コードの改修がしやすくなることが分かりました。
ただ、独特な書式だったり、書き方によってはパフォーマンスが落ちたりするので、扱う際は注意が必要です。

具体的なModelの作り方については今回は省きましたが、別の記事で紹介する予定です。

# 参考記事

[React使い必見！ Immutable.jsでReactはもっと良くなる](https://www.wantedly.com/companies/wantedly/post_articles/28935)

[Immutable.jsでreact+redux環境が楽になりそうな話](http://www.gakusmemo.com/?p=701)

[Immutable.jsでReduxにModel層を導入しビジネスロジックを集約する](http://post.simplie.jp/posts/81)

[Reduxのパターンとアンチパターン](http://postd.cc/redux-patterns-and-anti-patterns/)

[Using Immutable.JS with Redux](http://redux.js.org/docs/recipes/UsingImmutableJS.html)

[Estado inmutable con Redux e Immutable.js](https://medium.com/react-redux/estado-inmutable-con-redux-e-immutable-js-5a3d69ef0451)