recompose
recomposeはHigher-order Components(HOC)を作るためのユーティリティパッケージです。
HOCはコンポーネントを引数にとり、付加価値のついたコンポーネントを返却する関数です。UI実装の世界で利用できる高階関数(higher-order function)と言えます。
HOCを利用することでコンポーネントに含まれるロジックを共有したり、React実装の要であるStateやPropsをより柔軟に扱うことができるようになります。Reactを利用した実装において、より高度な設計を保つために重要な考え方の一つでしょう。
今回はrecompose入門のために、すぐに始められそうな取り組みとredux実装との共存を考えます。
compose
まずHOCの基本的な概念は以下のとおりです。
const Enhanced = higherOrder(Wrapped)
// Wrapped -> 元になるコンポーネント。多くの場合Stateless Functional Component(SFC)である
// higherOrder -> なんらかの処理を施すHOC
// Enhanced -> HOCによって生成される新たなコンポーネント
これに対し複数のHOCが存在する場合は以下のようになります。入れ子状態になり怪しい雰囲気が漂ってきました。
const Enhanced = advancedHigherOrder(higherOrder(Wrapped))
recomposeから提供される compose()
を利用すると、これを次のように書き換えることができます。
import { compose } from 'recompose'
...
const Enhanced = compose(advancedHigherOrder, higherOrder)(Wrapped)
composeを使うことで、見通しが良いままにHOCを重ねることができます。
pure
まずは pure
を導入してみます。これはPropsが更新されない限り無駄な再レンダリングを抑制する働きがあります。
先ほどのcomposeを利用すると、exportする直前に挟んでおくだけで大きな変更をせずにコンポーネントに適用できます。
import { compose, pure } from 'recompose'
...
export default compose(pure)(Component)
withState, withHandlers
描画するUIは同一なのに、扱う状態だけが異なる場合などに有効なのがwithStateとwithHandlersです。classを使わずとも、HOCの仕組みを使ってSFCに対してStateとそのイベントハンドラを定義できます。
これもcomposeを使うことで以下のように適用できます。
import { compose, withState, withHandlers } from 'recompose'
...
const enhancer = compose(
withState('count', 'updateCount', 0),
withHandlers(
increment: ({ updateCount }) => () => updateCount(count => count + 1)
)
)
export default enhancer(Component)
これまでStateを載せたい場合は、React.Componentを継承させたclassをわざわざ用意して、使わないライフサイクルメソッドが生えている状態になってしまっていました。recomposeを使えば、本当に必要な機能だけを後から被せることが簡単に実現できます。
withStateは第一引数に付与するState名、第二引数にそのStateを更新する関数名、第三引数に初期値を定義できます。withHandlersではPropsを引数にとることが出来ます。ここではwithStateで作成した更新する関数( updateCount
)も取得できるため、値の更新イベントハンドラを定義できます。
これらはすべて通常のPropsと同様にコンポーネントへ渡されます。
さらにこれに pure
を挟むことも可能です。
import { compose, withState, withHandlers, pure } from 'recompose'
...
const enhancer = compose(
withState(/* ... */),
withHandlers(/* ... */),
pure
)
export default enhancer(Component)
reduxと同居させる
Reactアプリケーションにreduxを導入する場合、react-reduxを使用することが多いと思います。その場合 connect()
を使ってstateやdispatchを渡しますが、これもHOCが利用されています。
recomposeを使えば、同時にwithStateなども同じような形で併用することができます。
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { compose, withState, withHandlers } from 'recompose'
...
const enhancer = compose(
connect(
state => ({ msg: state.msg }),
dispatch => ({ actions: bindActionCreators(actions, dispatch) })
),
withState('count', 'updateCount', 0),
withHandlers(
increment: ({ updateCount }) => () => updateCount(count => count + 1)
)
)
export default enhancer(Component)
これの利点は、
- SFCとして作成しているComponentに対して副作用をまとめて扱える
- React.Componentを継承する必要がない
- 今までthis.stateとして定義していた値も、まとめてProps経由で渡すことができる
この構造でコンポーネントを作成していくと、自然とUI描画部分がSFCとして出来ている方が都合が良くなっていきます。
まとめ
ここで紹介したもの以外にも、便利な関数がたくさん用意されているようなので、
- recomposeから提供されている関数から気になったものを1つ調べる
- 自分が書いているプロダクトのどこに活かせるかを考える
- とりあえず1〜2箇所程度に反映してみる
- (繰り返し)
これで少しずつ学習しつつ、Reactアプリケーションの最適化を進めることができるなと感じました。
リファクタリング大好き民の皆さんにとってはなかなか面白いパッケージだと思います。