reduxの使い方を考える

業務で何度かreduxを実用している。

基本reduxは実用的で良いと思っている。

関数型的な考え方に抵抗があった人も、reduxを使い、思考を変えようとしているように見えるのは驚きだ。

一方、冗長に感じる箇所や妥協で書いてしまうパターンもある。どのように扱っていけばよいのだろうか?

sample repository: https://github.com/nakaji-dayo/redux-practice

actionについて

Actionは代数データ型を表現したかったものである。

// actions.js
const XXX_ACTION = 'XXX_ACTION'  
export const createXxxAction = (x) => ({  
  type: XXX_ACTION,
  x
})
  • 型 = action.js
  • コンストラクタ = createXxxAction
  • 他は内部実装

jsで普通に書くと明らかに冗長だし、createrに関しても何が嬉しいのかわからない。

いい感じにADTを使え、記述に見合うリターンがある方法を検討する。

要件

  • Actionを簡潔に宣言したい
  • 利用時に正しい引数を与えられているかチェックしたい
  • パタンマッチできたら簡潔にreducerかけてなお良い

選択肢

-- 要レビュー

  • sweet.js
    • 消え行く運命?
  • babel macro
    • ライブラリ少なくない?
    • (sweetsも含め)自由にmacro作れる系のもので、それを使ったライブラリが普及するのは無理な気がする。
  • elm
    • とても期待しているがjsではない
    • elm-loaderでファイルごとに使うのはできるが、そこで定義したものを使う各所ではチェックできなさそう。
  • flow
    • jsの範囲で使える
    • 大きくjsから逸れずに各所で型チェックできそう
    • syntaxがすごく良くなったりはしない

flowでのactionの改善

Union Typeを使って、Actionを表現

/* @flow */

export type Action  
  = {type: "COUNT_UP", value: number}
  | {type: "COUNT_DOWN", value: number}
  | {type: "HOGE", fuga: number}

export type Dispatch = Action => Action  

ほぼplainなjsとして使える

const mapDispatchToProps = (dispatch: Dispatch, props) => ({  
  countUp: () => {
    dispatch({type: "COUNT_UP", value: 1}) -- valueを指定しないとエラーとなる。
  },

これも型チェックされる。すごい。

  switch (action.type) {
    case "COUNT_DOWN":
      return {
        ...state,
        count: state.count - action.fuga // error property `fuga`. Property not found.
      }

Actionの型がチェックされるのはわざわざ宣言するのに足るメリットかと思う。

Actionのtypeも型レベルの文字列なので、毎回べた書きでも大きな問題はないと考える。クォート打つのは面倒だが。

余談

因みに私は、jsのみのプロジェクトでも、ActionCreaterは使わず、dispatch時にオブジェクトを毎回作っている

dispatch({  
  type: XXX,
  x,y,z
})
  • 型チェックされないコンストラクタって何
  • es2015のproperty name shorthandがあるのでほぼタイプ量変わらない
  • 関数にしておいたほうが使い勝手が良いケースはありそう。出会ってない。

componentについて

コンポーネントは可能な限りpure functionにしたい(stateless function componentと呼ぶらしい)。

  • stateを持っていないことが自明
  • virtual domという仕組みは(現状)必要だが、reactでなければいけないわけではない。

componentをReact classにする必要があるケース。

  • react componentのlife cycle eventを使いたい時
  • UIの範囲に閉じていて、他コンポーネントとの影響もない動作の時(言わば「ちょっとしたstate」)

解決方法

React.createClassを関数に入れて頑張る

recomposeを使う。

https://github.com/acdlite/recompose

const ColorBox = R.withState('color', 'setColor', 'ff0')(  
  ({color, setColor}) => (
    <div style={{backgroundColor: `#${color}`, width: 80, height: 80}}
         onClick={() => setColor(color.slice(1) + color[0])} >
    </div>
  )
)

reducerについて

ES2015からのsyntax大変良い。

確かにImmutableを保証したくはなるが、私はImmutable.jsを使うのは、記述力の低下な対して、得られる保証が見合ってないのではと思う。

一方、ちゃんとImmutableなデータ構造を使わないとパフォーマンスが悪いのはその通りで。 ケースバイケース。

-- 追記

正直jsの仕様でなんとかしてほしい。

または、リストやオブジェクト周りのリテラルをOverloadできれば良い。

-- ここまで

他副作用の扱いついて

saga middlewareがおすすめ

-- 要比較表

感想

不満に思ったところを調べていくとだいたい既にtryがあった。

みんなjsという縛りの中で頑張っている。

flow楽しそうだが、もうjsでなくて良いのではってなる。

elm, purescriptや他altjsの今後にも期待。