Reactコンポーネントで責務を分担するには(あんまり難しいことはなしで…)。
複雑なview等で、様々な状態をstateに保持したいとき、コンポーネントが肥大化してしまうのをなんとかしたい。(一応、storeには関係ない要素という想定)
'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() {...}
}
プロトタイプを拡張する方法
従来のようにprotptypeを拡張することもできるみたい。単に関数を分離したいとかならこれもありかも。
でも、今回はローカルstateも含めて責務を分離したいんですよね。aの関数と状態はAComponentに、bの関数と状態はBComponentに、みたいな感じで。
Mixinは死んだ
ES6だとモジュール入れないとmixinが使えません。不用意に依存モジュールを増やしたくない所です。また、今後mixinがサポートされることもなさそうです。
Higher-Order Componentsという方法が紹介されています。
こちらも参考になります。
コンポーネントの拡張
HOCを参考にしてコンポーネントを拡張するコードを書いてみました。
'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
メソッドも単純です。
'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>
)
}
}
※概念コードなので動作確認はしていません。