Reactコンポーネントで責務を分担する

Reactコンポーネントで責務を分担するには（あんまり難しいことはなしで…）。

複雑なview等で、様々な状態をstateに保持したいとき、コンポーネントが肥大化してしまうのをなんとかしたい。（一応、storeには関係ない要素という想定）

```js

'use strict'

import React, { Component } from 'react'

class SmartComponent extends Component {
  constructor(props) {
    super(props)
    this.state = {
      // stateたくさん
      hoge: 0,
      fuga: 1,
      piyo: 2,
      ...
    }
  }
  // state操作たくさん
  operateHoge() {...}
  operateHoge2() {...}
  operateFuga() {...}
  operateFuga2() {...}
  operatePiyo() {...}
  ...
  // イベントハンドリングたくさん
  handleClick() {...}
  handleMouseEnter() {...}
  handleMouseLeave() {...}
  handleClickHoge() {...}
  handleClickFuga() {...}
  ...
  // Component Lifecycleも入る・・・
  componentDidMount() {...}
  componentWillReceiveProps() {...}
  shouldComponentUpdate() {...}
  ...
  render() {...}
}

```

# プロトタイプを拡張する方法

- [React higher order components（※導入部分）](http://www.darul.io/post/2016-01-05_react-higher-order-components)

従来のようにprotptypeを拡張することもできるみたい。単に関数を分離したいとかならこれもありかも。

でも、今回はローカルstateも含めて責務を分離したいんですよね。aの関数と状態はAComponentに、bの関数と状態はBComponentに、みたいな感じで。

# Mixinは死んだ

ES6だとモジュール入れないとmixinが使えません。不用意に依存モジュールを増やしたくない所です。また、今後mixinがサポートされることもなさそうです。

- [Mixins Are Dead. Long Live Composition](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.cjqjr1b76)

Higher-Order Componentsという方法が紹介されています。

こちらも参考になります。

- [Higher-Order Componetを使ってES6 Class記法でmixinっぽいことをする](http://qiita.com/pirosikick/items/9a32933e1afcb4a2b38f)

# コンポーネントの拡張

HOCを参考にしてコンポーネントを拡張するコードを書いてみました。

```js

'use strict'

import React, { Component } from 'react'
import PresentationComponent from './presentation-component'

// hogeについての責務
function connectHogeDecorator(InnerComponent) {
  class HogeDecorator extends Component {
    constructor(props) {
      super(props)
      this.state = { hoge: 0 }
    }
    hogeOperator() {
      // operate hoge
    }
    handleClickHoge(e) {
      this.hogeOperator()
    }
    render() {
      return (
        <InnerComponent {...this.props} {...this.state}
          onClickHoge={this.handleClickHoge.bind(this)}
        />
      )
    }
  }
  return HogeDecorator
}

// fugaについての責務
function connectFugaDecorator(InnerComponent) {
  class FugaDecorator extends Component {
    constructor(props) {
      super(props)
      this.state = { fuga: 1 }
    }
    fugaOperator() {
      // operate fuga
    }
    handleClickFuga(e) {
      this.fugaOperator()
    }
    render() {
      return (
        <InnerComponent {...this.props} {...this.state}
          onClickFuga={this.handleClickFuga.bind(this)}
        />
      )
    }
  }
  return FugaDecorator
}

// ベースコンポーネント
export default class SmartComponent extends Component {
  constructor(props) {
    super(props)
    this.state = { piyo: 0 }
  }
  piyoOperator() {
    // operate piyo
    const { hoge, fuga } = this.props
    this.setState({
      ...this.state,
      piyo: hoge + fuga
  }
  handleClick(e) {
    this.piyoOperator()
  }
  render() {
    return (
      // ビュー
      <PresentationComponent {...this.props} {...this.state}
        onClick={this.handleClick.bind(this)}
        onClickHoge={this.props.onClickHoge.bind(this)}
        onClickFuga={this.props.onClickFuga.bind(this)}
      />
    ))
  }
}

// 親コンポーネントで

import connectHogeDecorator from './smart-component'
import connectFugaDecorator from './smart-component'
import SmartComponent from './smart-component'

class ParentComponent extends Component {
  render() {
    return connectHogeDecorator(connectFugaDecorator(
      <SmartComponent {...this.props} />
    ))
  }
}

```

※概念コードなので動作確認はしていません。

方法は単純で、関数の引数をコンポーネントにして、そのコンポーネントを`render`するコンポーネントを返す、その時にstateやpropsを直にassignするという形です。誤ってメンバーを上書きする可能性はありますが…そのへんはよしなに。

これで、分割したコンポーネントのstateやpropsをベースコンポーネントにきれいに集約できそうです。

# コンポーネントの入れ子で実現してみる

試しに、コンポーネントの入れ子で実現すると、`cloneElement`まわりが煩雑になりがちで、不要な`div`タグも出てきます。Componentを返す関数を使うほうが`div`タグは必要なときだけ入れられますし、`render`メソッドも単純です。

```js

'use strict'

import React, { Component, cloneElement } from 'react'
import PresentationComponent from './presentation-component'

// hogeについての責務
export default class HogeDecorator extends Component {
  constructor(props) {
    super(props)
    this.state = { hoge: 0 }
  }
  hogeOperator() {
    // operate hoge
  }
  handleClickHoge(e) {
    this.hogeOperator()
  }
  render() {
    return (
      <div>
        {this.props.children && cloneElement(this.props.children, {
          ...this.state,
          ...this.props,
          onClickHoge: this.handleClickHoge.bind(this)
        })}
      </div>
    )
  }
}

// fugaについての責務
export default class FugaDecorator extends Component {
  constructor(props) {
    super(props)
    this.state = { fuga: 1 }
  }
  fugaOperator() {
    // operate fuga
  }
  handleClickFuga(e) {
    this.fugaOperator()
  }
  render() {
    return (
      <div>
        {this.props.children && cloneElement(this.props.children, {
          ...this.state,
          ...this.props,
          onClickFuga: this.handleClickFuga.bind(this)
        })}
      </div>
    )
  }
}

// ベースコンポーネント
export default class SmartComponent extends Component {
  constructor(props) {
    super(props)
    this.state = { piyo: 0 }
  }
  piyoOperator() {
    // operate piyo
    const { hoge, fuga } = this.props
    this.setState({
      ...this.state,
      piyo: hoge + fuga
  }
  handleClick(e) {
    this.piyoOperator()
  }
  render() {
    return (
      // ビュー
      <PresentationComponent {...this.props} {...this.state}
        onClick={this.handleClick.bind(this)}
        onClickHoge={this.props.onClickHoge.bind(this)}
        onClickFuga={this.props.onClickFuga.bind(this)}
      />
    ))
  }
}

// 親コンポーネントで

import HogeDecorator from './smart-component'
import FugaDecorator from './smart-component'
import SmartComponent from './smart-component'

class ParentComponent extends Component {
  render() {
    return (
      <HogeDecorator {...this.props}>
        <FugaDecorator>
          <SmartComponent />
        </FugaDecorator>
      </HogeDecorator>
    )
  }
}

```

※概念コードなので動作確認はしていません。