Reduxでフロントエンドを実装する際、ビジネスロジックをどこに書くか、という問題がしばしば起こります。 ここでは、Immutable.jsを用いてModel層を設計する方法についてまとめました。

Fluxにおけるビジネスロジック

ビジネスロジックとは、簡単にいうとドメイン層に関するロジック、MVCでいうとModelが担当するロジックです。 FluxアーキテクチャではStoreがこれを担当します。

ではStoreにビジネスロジックを書けばいいかというと、Storeは単一ではなくたくさんのオブジェクトの状態を管理するので、たちまち肥大化してしまいます。

また、実装方法自体が特に規定されている訳ではないので、Action/Store/Viewどこにでも書けてしまいます。 責務をはっきりさせないと、再利用性などの問題が生じます。

どこに書く?

それでは、ビジネスロジックはどこに書けばよいのでしょうか。 結論からいうと、ドメインを抽象化したModel層を設け、そこに書く方法があります。

ユースケースとしては、Storeにおける状態をModelのインスタンスとして扱い、これをとおしてビジネスロジックの操作を担う、などが挙げられます。

各レイヤーでのビジネスロジックの操作について考えると、ViewにはStoreからStateが渡るので、それをとおして操作できます。 ActionはStoreに対して状態を変更する命令を出せるので、これをとおして行なえます。

こうすれば、ビジネスロジックを司る責務が明示的になり、前述の問題点を解消することができます。

Immutable.jsでModelを書く

Immutable.jsはJavaScriptでイミュータブルなオブジェクトを扱うためのライブラリで、「対象を抽象化する」という役割を持つModelの実装に最適です。

これ以外にも、いくつかの副次的なメリットがあります。 これは、次に示す例から見えてきます。

コード例

ここでは、ToDoのアイテムの完了/未完了をトグルするだけの簡単な処理の、Reduxでの実装例を示します。 Immutable.js導入のメリットを示すことが目的なので、いくつかの手順を簡略化しています。

Model

Immutable.Record()でレコードを作成し、それを継承したクラスをModelとして定義します。

それぞれのインスタンスに関するビジネスロジックは、インスタンスメソッドとして定義していきます。

models/ToDo.js:

import { Record } from 'immutable';

const ToDoRecord = Record({
  id: null,
  text: '',
  completed: false
});

export default class ToDo extends ToDoRecord {
  getText() {
    return this.get('text') || 'New ToDo';
  }
}

Action

Actionでは、操作対象のアイテムを受け取って、完了ステータスをトグルします。

actions/ToDo.js:

export function toggle(todo) {
  return {
    type: 'TOGGLE',
    completed: !todo.completed
  };
}

Reducer

Reducerでは、Actionに対応するメッセージを受け取り、ToDoインスタンスのset()メソッドで新しいインスタンスを返します。

初期状態としてnew ToDo()としていることから分かるとおり、状態をImmutableオブジェクトとして扱っています。

reducers/todo.js:

import ToDo from '../models/ToDo';

export default function (state = new ToDo(), action) {
  switch (action.type) {
    case 'TOGGLE':
      return state.set('completed', action.completed);
    default:
      return state;
  }
}

View

StateのtodoとActionのtoggle()は、それぞれthis.propsにマップしているものとします。

ここで、todoはToDoモデルのインスタンスなので、getText()でデフォルト値を取得できます。

components/ToDoComponent.js:

import React, { Component } from 'react';

class ToDoComponent extends Component {
  render() {
    const { todo, toggle } = this.props;
    return (
      <div>
        <input
          type='checkbox'
          checked={todo.completed}
          onChange={toggle(todo)}
        />
        {todo.getText()}
      </div>
    );
  }
}

メリット

コード例から読み取れるように、Immutable.jsを導入することで、次のようなメリットが得られます。

  • ビジネスロジックをModelに集約できる
    • ロジックの場所が明示的になる
  • Reducerでstate.set()のように状態の変更を直感的に書ける
    • Object.assign()を書かなくてよい
  • イミュータブルなので、オブジェクトの変更可能性を考慮しなくてよい

参考記事

おわりに

ReduxにImmutable.jsを導入することで、ビジネスロジックを集約でき、きれいなコードが書けるようになりました。

Reduxでアーキテクチャを設計するときの参考になればと思います。