LoginSignup
37
20

More than 5 years have passed since last update.

render prop と HOC について

Last updated at Posted at 2018-01-08

react-router の mjackson が HOC を批判していたので、自分の考えを書いておきます。

Use a Render Prop! - componentDidBlog https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce

mjackson の主張の要約

  • ES2015 Classes は mixin 的な振る舞いをサポートしていない
  • HOC は 新しい mixin だ

主張1: HOC は自身の振る舞いを自己規定するものである

const connector = ReactRedux.connect(...)
compose(
  connector,
  pure,
  lifecycle({
    componentDidMount() {
      console.log('mounted')
    }
  })
)(Counter)

これは

  • store に connect され
  • pure であり
  • lifecylcle をもつ

というのは自己完結していて、外から与えられる属性ではなく、内側へ向けた定義です。ReactRedux の connect は文脈無視して props をシングルトンな store から捻り出すので、親から与えられる属性ではなく、自己定義的です。

自分は すべての Component が Stateless Functional Component として定義されるべきだと思ってるので、振る舞いを明示的に切り離す HOC は有意義であると思っています。

主張2: render props はライブラリが外から子の振る舞い(props)を規定するものである

とはいえ mjackson の主張に則れば Redux の connector は次のように書き直すこともできそう。

<Connector
  mapStateToProps={state => state.counter}
  render={props => <Counter value={props.value}/>}
/>

こういうパターンも見ますね。

<Connector
  mapStateToProps={state => state.counter}
>
  {props => <Counter value={props.value}/>}
</Connector>

これはAPIスタイル問題で、ライブラリ作者がどれを選ぶかという問題だと思うんですが、ユーザー側が選べる問題ではなく、
ライブラリ作者はとりあえず render props を採用する、というのはアリだと思います。

自己定義的なHOCはこれらのパターンからハズレて、別の役割を持っていると思います。

自分の思う HOC アンチパターン

とはいえ HOC が無駄に物事を複雑にするケースがあるという主張は理解できます。
例えばこういう形式のコードです。

compose(
  (hocA: HOC<A, B>),
  (hocB: HOC<B, C>),
  (hocC: HOC<C, D>)
): HOC<A, D>

これらが組み合わさって複雑になるときに、それぞれのHOCの順序に依存する、手を付けられない魔物が生まれるのは何度か経験しました。たとえば form 系ライブラリのバインディングと mapStateToProps が混ざるときです。

なので、 「state を扱う HOC は一個までとする」 みたいな縛りが必要だとは思っています。

まとめ

  • 暗黙に他のhocの順序または親のpropsに依存する HOC はいずれにせよアンチパターンである。
  • HOC は自己規定であり、render prop はライブラリの削除
  • ライブラリ作者は render prop で実装したほうがいい
37
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
20