ReduxのModelとしてRamda.jsを使う(TypeScript)

前提知識として

  • Redux(Fluxアーキテクチャ)の知見
  • Immutable.jsでModel層を作成することの知見
が必要となります。

導入までの流れ

つい先日、某プロジェクトのSPAの基盤を作成することになりました。
幸いにも1年ほど前、SPAでの実践経験があったので同様のアーキテクチャで行けると高を括っていました。
ですが、今回はTypeScriptという事もあり、前回とは多少変えざるを得ない状況となりました。

1年前のアーキテクチャ

  • es2015 ~
  • React/Redux
  • Immutable.js
  • redux-thunk
  • react-router/react-router-redux
  • css modules

etc…

今回のアーキテクチャ

  • es2015 ~ -> TypeScript
  • React/Redux -> React/Redux + typescript-fsa / typescript-fsa-reducers
  • Immutable.js -> Ramda.js
  • redux-thunk -> そのまま
  • react-router/react-router-redux -> react-router/connected-react-router
  • css modules -> styled-component

etc…

という、変更となりました。
この中で Ramda.js について紹介したいと思います。

Immutable.jsをTypeScriptに導入しようとしたが…

Redux(Fluxアーキテクチャ)においてreducerにてデータの整形・加工などすることになるかと思います。
簡単なTODOアプリ等であればそれで問題ないのですが、規模が大きくなるにつれてModel的な役割をするレイヤーに責務を逃す事があるかと思います。
そこで、Immutable.jsを用いてModel層を作るのはご存知の方も多いかと思います。
当初、私もその戦略で行こうと思ったのですが、

 TypeScript  での  Immutable.js の型定義が微妙じゃね。。。

というまさかの結果に(あくまでも個人的感想です)

選定当時は Immutable.js v4  が出るのか?出ないのか?(v4だと型定義がちょっとは良くなっている?感じだが、まだまだな予感)という状況でしたので断念しました。
具体的にいうと

 personal.data.json 

{
  tongari: {
    gender: 'male',
    age: 39,
  },
}

のようなデータがあった際

 component.tsx 

tongari.get('hoge') // -> 本来ならばtslintで怒られたい

が可能となってしまう為です。
これは型定義ファイルのほうが any で型定義されているためです。
※本来ならジェネリクスを受けつけるように定義されていれば問題なかったはず。

この、解決策としては
 型定義ファイルを上書きする  という選択肢もありましたが、他の型定義がどうなってるのかを調べるのも面倒でありましたし、Immutable.jsでのバージョンが上がった際への対応も考慮しないといけないのでこの方法は使いませんでした。

ここで、Immutable.jsに期待していた事を再確認してみます。

  • Immutableなオブジェクトを返却してくれる事
  • Immutable.jsのようなデータ操作ができる事
この2点の代替として Ramda.js を思い浮かべました。

Ramda.jsを使ってみる

まずは以下のコードをご確認ください。

 personal.data.json 

{
  tongari: {
    gender: 'male',
    age: 39,
  },
}

 component.tsx 

tongari.hoge // 当然、tslintで怒られる

 model.ts 

public setPersonalData(state: IAppModel, params: IPersonal) {
  const path: [TAppModelKey, TPersonalMapKey, TPersonalKey] = [
    'personalData',
    'tongari',
    'age', // もしここで 'hoge' を指定するとちゃんとtslintで怒られる。
  ]
  return R.assocPath(
    path,
    params.age
  )(state as any)
}

また、他にもメリットが見えてきました

  • Immutable.jsで使用していたような toJS とか fromJS をしなくてよい(TypeScriptのインターフェース定義でプロパティをreadonlyでImmutableにしてるため変換する必要がない)
  • get(‘hoge’) などの記述が不要
  • Immutable.jsに劣らないほどにデータ操作系メソッドが充実してる。(むしろ、Ramda.jsのほうが豊富)

デメリットとしては

  • Immutable.jsのようにgetter的なものを作成できない。

がしかし、初期データから何かしらのデータ操作を行うときは必ず、actionを発行するはずなのでactionを受け取った際のタイミングでModelにてデータを操作してstoreを変更すれば良いので特段問題にならない気がしております。

まとめ

少々、マニアックな記事でしたが一例としてご紹介させて頂きました。
TypeScriptは強力ではあるのですがES2015〜の際使っていたライブラリ等を使う際は一旦、調査するのは必須だなと感じる一件となりました。

雑に作ったサンプルコードはこちら

最後まで読んで頂きありがとうございました。